amenzhinsky / iothub Goto Github PK
View Code? Open in Web Editor NEWAzure IoT Hub SDK for Golang
License: MIT License
Azure IoT Hub SDK for Golang
License: MIT License
Does anyone have any inputs/objections to an alternate direct method implementation that can change the reply to be asynchronous from the callback? I have a use case where I need processing time to handle the callback and I don't want to hold the context, I want to send the reply at a later date.
Possible changes:
I prefer #1 because it is easy to pass the ID between async processing.
Setting the OnConnection listener on the MQTT Transports breaks subscriptions, because the library relies on it, so it is impossible to get notifications about disconnect. For disconnects, it is possible to customize the OnConnectionLost as such:
opts := mqtt.WithClientOptionsConfig(func(opts *paho.ClientOptions) {
opts.OnConnectionLost = func(client paho.Client, e error) {
onDisconnect(e)
}
})
It would be nice if there was someway to get OnConnect callbacks.
I'm looking to see if it's possible to disconnect/reconnect with new credentials?
For a long running connection my x509 cert may expire and my program is able to acquire a new one but then I'll need to restart my connection with the new cert. Wondering if this is possible without creating a new client. So far I'm had no luck getting this to work. I'm currently trying to call Close() on the transport and then Connect() passing it the new credentials. After re-connection nothing is making it to the cloud.
I'm trying to receive C2D messages. When I send the message from the Azure portal I get the following error:
2022/10/19 17:39:16 ERROR message parse error: invalid semicolon separator in query
Here is the program:
func main() {
c, err := iotdevice.NewFromX509FromFile(iotmqtt.New(), "mydevice", "xxx", "xxx", "xxx")
if err != nil {
log.Fatal(err)
}
if err = c.Connect(context.Background()); err != nil {
log.Fatal(err)
}
esub, err := c.SubscribeEvents(context.Background())
go func() {
for {
msg := <-esub.C()
fmt.Printf(">> %+v\n", msg)
}
}()
termsig := make(chan os.Signal, 1)
signal.Notify(termsig, syscall.SIGINT, syscall.SIGTERM)
<-termsig
}
I tried sending ASCII text and JSON for the portal. How could I receive C2D messages? I'm looking to use the iotdevice
module rather than the iotservice
module.
I'm trying to connect and it appears things are failing on the connection string. My connection string works with the C SDK.
This is the error I'm seeing:
2022/10/10 17:51:44 SharedAccessKey is required
My connection string:
HostName=decog-iothub-eastus-###.azure-devices.net;DeviceId=mjohn-1;x509=true
I'm creating a cloud-to-device link via AMQP with:
ihQueueClient, err := iotservice.NewFromConnectionString(iotHubConnStr)
I can use this successfully for 1 hour or so, but then after that I start seeing:
*Error{Condition: amqp:unauthorized-access, Description: Token or Certificate is invalid., Info: map[com.microsoft:is-filtered:true com.microsoft:tracking-id:<<<TRACKING_ID>>>-G:6-TimeStamp:02/10/2021 16:05:08]}
Looks like the AMQP connection is being dropped due to inactivity or just because 1 hour is passed after it has been created. Is there any way I can avoid this? How can I specify to automatically refresh or reconnect to the IoT Hub?
I can see in here
Line 44 in e718b48
there's this TLS option, but it's not exactly related to timeouts, that's more about extra security checks when using TLS:
// WithTLSConfig sets TLS config that's used by REST HTTP and AMQP clients.
func WithTLSConfig(config *tls.Config) ClientOption {
return func(c *Client) {
c.tls = config
}
}
Because of how The Direct Method handler is implemented, there is no opportunity for asynchrony without blocking concurrent or future direct method requests. Ideally, instead of :
type DirectMethodHandler func(p map[string]interface{}) (map[string]interface{}, error)
It was something like:
type DirectMethodHandler func(input map[string]interface{},completion func(map[string]interface{}, error))
Or something channel based.
Hi there, recently I found this SDK, and it's awesome! However, when I use *Client.SendEvent
in iotservice
package, I encountered a weird problem, 50 minutes after calling this function, a log comes out as follows:
2021/03/22 11:41:47 ERROR put token error: amqp: session closed
And I find a issue explaning this: #38 (comment) . As the comment explains, *Client.SendEvent
will result in calling *Client.putTokenContinuously
:
// putTokenContinuously in v0.7.0
func (c *Client) putTokenContinuously(ctx context.Context, conn *amqp.Client) error {
const (
tokenUpdateInterval = time.Hour
// we need to update tokens before they expire to prevent disconnects
// from azure, without interrupting the message flow
tokenUpdateSpan = 10 * time.Minute
)
sess, err := conn.NewSession()
if err != nil {
return err
}
defer sess.Close(context.Background())
if err := c.putToken(ctx, sess, tokenUpdateInterval); err != nil {
return err
}
go func() {
ticker := time.NewTimer(tokenUpdateInterval - tokenUpdateSpan)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := c.putToken(context.Background(), sess, tokenUpdateInterval); err != nil {
c.logger.Errorf("put token error: %s", err)
return
}
ticker.Reset(tokenUpdateInterval - tokenUpdateSpan)
c.logger.Debugf("token updated")
case <-c.done:
return
}
}
}()
return nil
}
This function will start a new goroutine to putToken
to update session token every 50 minutes (tokenUpdateInterval
- tokenUpdateSpan
). However, defer sess.Close(context.Background())
means that the session will be closed when putTokenContinuously
returns. As a result, putToken
called in the goroutine will always fail (which will log: put token error: amqp: session closed
). I notice that this problem has been fixed in recent commit (though not released yet), but here's another question:
Each time I call the *Client.SendEvent()
, it will start a new session and start a goroutine to putToken
every 50 minutes until the Client
is closed, which means each time I send an event, a new endless goroutine is created to putToken
continuously, but all I need is just sending an event, after that, I don't need to maintain this session anymore (because next time I call *Client.SendEvent()
, it will start another new session for me). What's more, as I have to call *Client.SendEvent()
frequently, it could lead to a large number of goroutine leak.
My solution to this problem is that the *Client.newSession()
called by *Client.SendEvent()
don't call putTokenContinuously
, it just call putToken
and return the new session.
The current implementation uses 'text/plain' as the content type for the sign requests, but from 1.3.0 the identity-service expects the content-type to be 'application/json'.
Hi,
I've been using this library for almost a year and find it very useful. But I am confused about a panic state I get when trying to send from service to device via iotservice.SendEvent()
I get the following log whenever SendEvent gets called:
D connecting to SERVER_NAME.azure-devices.net
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x10 pc=0x6fefe0]`
I presume I am not setting something correctly, but I'm not sure what.
Example code in the documentation would be helpful and appreciated.
Here is what I am doing:
var err error
iotServiceClient, err = iotservice.New(
iotservice.WithConnectionString(IOT_SERVICE_CONNECTION_STRING),
iotservice.WithLogger(common.NewLogWrapper(true)),
)
if err != nil {
log.Fatal("ioterr", err)
}
m := socketMessage{}
m.Command = "hello"
toBytes, err := json.Marshal(m)
if err != nil {
log.Fatal("json", err)
}
iotServiceClient.SendEvent(context.Background(), "device123", toBytes)
I'm trying to install the cli with the command GO111MODULE=on go get -u github.com/amenzhinsky/iothub/cmd/{iothub-service,iothub-device}
the iothub-divice installation was successful, but the iothub-service won't install, any one with this behaviour?
this is the output:
GO111MODULE=on go get -u github.com/amenzhinsky/iothub/cmd/{iothub-service,iothub-device}
go: found github.com/amenzhinsky/iothub/cmd/iothub-device in github.com/amenzhinsky/iothub v0.6.0
go: found github.com/amenzhinsky/iothub/cmd/iothub-service in github.com/amenzhinsky/iothub v0.6.0
go: github.com/Azure/go-amqp upgrade => v0.13.1
go: github.com/pkg/errors upgrade => v0.9.1
go: golang.org/x/net upgrade => v0.0.0-20200707034311-ab3426394381
github.com/amenzhinsky/iothub/eventhub
../pkg/mod/github.com/amenzhinsky/[email protected]/eventhub/client.go:266:22: not enough arguments in call to msg.Accept
have ()
want (context.Context)
Thank you for creating this library :)
I am using method payloads (and responses) that are not in JSON form, but base64 strings (strictly speaking still valid json) as my connected devices use cellular data, so I want to keep data usage to an absolute minimum. However, the MethodCall.Payload in this library is a map[string]interface{}
. I forked your library to change it into just an interface{}
.
Are you interested in a pull request? (and if so, would you prefer a new method (to keep it backwards compatible) or a change to MethodCall
and MethodResponse
?)
Version:
github.com/amenzhinsky/iothub v0.7.0
Error:
Link detached, reason: *Error{Condition: amqp:link:detach-forced, Description: The link 'G2:...' is force detached. Code: ServerError. Details: AmqpEventHubConsumer.IdleTimerExpired: Idle timeout: 00:30:00.
I am getting this error message and restarting the pod Kubernetes container frequently. I had this code working for a long time and it did not happen so frequently. The first time, I thought that it was idle, but I checked the last message received was one minute before and not 30 minutes, maybe I misunderstand the Azure concept of idle in the Iothub bus and be observing the wrong parameters.
My contantainer log is:
...
{"time":"2021-05-01T06:17:58.627919145Z", "message received"}
{"time":"2021-05-01T06:17:58.633331806Z", "message received"}
{"time":"2021-05-01T06:17:58.638524857Z", "message received"}
{"time":"2021-05-01T06:18:02.227702684Z","level":"FATAL","prefix":"-","file":"main.go","line":"47","message":"link detached, reason: *Error{Condition: amqp:link:detach-forced, Description: The link 'G2:omited' is force detached. Code: ServerError. Details: AmqpEventHubConsumer.IdleTimerExpired: Idle timeout: 00:30:00.}
My golang code is:
parentCtx, _ := context.WithCancel(context.Background())
log.Fatal(iotHub.SubscribeEvents(parentCtx, func(msg *iotservice.Event) error {
err = json.Unmarshal(msg.Payload, &identifier)
log.Info(" identifier :", identifier)
if err == nil {
HandleID(identifier)
} else {
log.Error("could not marshing identifier: ", err)
}
return nil
}
I am considering doing something like the following draft, but a little afraid of losing messages, now if there is an error I see due to the container reinitialization and fix it as soon as possible to do not lose messages.
draft:
for {
err = iotHub.SubscribeEvents(parentCtx, HandleFuncHere)
if err != nil {
if err == context.DeadlineExceeded { // IdleTimerExpired?
log.Errorf("iothub idle timeout")
} else {
log.Fatal(err) // another error
}
}
continue
}
This is a reasonable solution? Did someone experience something similar?
I appreciate any help that someone provides to workaround or understand better this issue, thanks :)
Version: v0.7.0
Transport: MQTT
Minimal example to reproduce.
func sendEvent(hub *iotdevice.Client) {
_ := hub.SendEvent(context.Background(),
[]byte("whatever"),
iotdevice.WithSendProperties(
map[string]string{
"te st": "to st",
},
),
)
}
Please notice, that custom property contains space (' ') in both key and value. It is not forbidden by IoT Hub messaging contract (https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-construct).
Attempt to send such message will result in infinite re-connect loop:
2021/10/28 15:50:06 DEBUG connection established
2021/10/28 15:50:06 DEBUG connection lost: EOF
2021/10/28 15:50:06 DEBUG connection established
2021/10/28 15:50:06 DEBUG connection lost: EOF
2021/10/28 15:50:07 DEBUG connection established
2021/10/28 15:50:07 DEBUG connection lost: read tcp 192.168.88.99:38798->40.113.176.167:8883: read: connection reset by peer
2021/10/28 15:50:07 DEBUG connection established
2021/10/28 15:50:07 DEBUG connection lost: EOF
2021/10/28 15:50:07 DEBUG connection established
2021/10/28 15:50:07 DEBUG connection lost: EOF
2021/10/28 15:50:08 DEBUG connection established
2021/10/28 15:50:08 DEBUG connection lost: read tcp 192.168.88.99:38804->40.113.176.167:8883: read: connection reset by peer
2021/10/28 15:50:08 DEBUG connection established
2021/10/28 15:50:08 DEBUG connection lost: EOF
2021/10/28 15:50:09 DEBUG connection established
2021/10/28 15:50:09 DEBUG connection lost: EOF
2021/10/28 15:50:09 DEBUG connection established
2021/10/28 15:50:09 DEBUG connection lost: EOF
2021/10/28 15:50:09 DEBUG connection established
2021/10/28 15:50:09 DEBUG connection lost: EOF
2021/10/28 15:50:09 DEBUG connection established
2021/10/28 15:50:09 DEBUG connection lost: EOF
This happens because in (github.com/amenzhinsky/[email protected]/iotdevice/transport/mqtt/mqtt.go), properties are being incorrectly encoded. Spaces are replaced with '+' char, which is a wildcard in MQTT protocol, and leads to immediate connection drop. Since D2C message was not sent, after re-connect library will attempt to push this message once again leading to another connection drop. Cycle repeats forever.
Incorrectly encoded topic looks like this: devices/my_device/messages/events/te+st=to+st
While Python and C++ SDKs produce correctly encoded: devices/my_device/messages/events/te%20st=to%20st
Suggested fix is to use encoding library different from 'net/url' or replace '+' with "%20" on top of it.
C2D commands are getting notified in the client. But, as per the protocol, client application is expected to "Complete" the message so that it will be deleted from the queue.
E.g.: in .NET SDK
https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/src/DeviceClient.cs#L362
I'm trying to update a field in my twin and I'm getting a 400 error back. I'm using MQTT.
"request failed with 400 response code"
The code:
d, r, e := c.RetrieveTwinState(context.Background())
if e != nil {
log.Errorw("Failed to retrieve twin", "error", e)
}
fmt.Printf("DESIRED: %+v\nREPORTED: %+v\n", d, r)
r["lastConnectionTime"] = time.Now().UTC().Format(time.RFC3339)
if v, e := c.UpdateTwinState(context.Background(), r); e != nil {
log.Errorw("Failed to update twin", "error", e)
} else {
log.Debugw("Successfully updated twin", "version", v)
}
Output:
DESIRED: map[$version:1]
REPORTED: map[$version:106 deviceInfo:map[components:[map[hwpn:000-00000-00 hwrev:000 infoelem:0 nodeid:17 serialnum:0 swpn:081-10010-00 swrev:00.05.00 typeid:0]] timestamp:2022-10-20T18:13:38Z] lastConnectionTime:2022-10-20T17:32:46+00:00Z powerFailure:map[timestamp:2022-10-20T14:21:40Z] productType:245]
{"level":"error","ts":1666290220.208017,"logger":"main.heartbeat","caller":"azure-go-telem/heartbeat.go:22","msg":"Failed to update twin","error":"request failed with 400 response code","stacktrace":"main.heartbeat\n\t/home/mjohn/workspace/connectivity-module/apps/azure-go-telem/heartbeat.go:22"}
Device can buffer up messages and send them all at some frequency. To accomplish, you can set CreationTimeUtc (payload creation time) in the event message. common.Message has this field, but looks like it is not set in the transport.send() https://github.com/amenzhinsky/iothub/blob/master/iotdevice/transport/mqtt/mqtt.go#L480
You need to set "$.ctime". Similarly other properties need to be honored like https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs#L1197
To use websockets, I successfully changed the following line manually according to according to MicrosoftDocs/azure-docs#21306:
iothub/iotdevice/transport/mqtt/mqtt.go
Line 107 in e12c1ec
o.AddBroker("wss://" + creds.GetHostName() + ":443/$iothub/websocket")
I think this should be possible to do through an option though, as in some cases it's e.g. impossible to use outgoing port 8883.
Hi, couldn't see is you repository fork of https://github.com/husarlabs/iothub looks like is the same?
Hi,
I was just running into issue due to the behaviour of Go Modules. It will grab the latest tag
and not the latest commit. So people will run maybe as I into problems that the function iotservice.FromConnectionString()
is not defined.
Cheers!
Hi,
I was just using your example code for the service component and received an amqp:link:redirect
error. In the documentation of AMQP this error is about the problem of not resolving the address to the container but everything in the response map look okay to me.
I just set up a fresh IoT Hub and created a device by hand and trying to run those examples on my local machine. The device code is working.
Any hints? :)
Is it possible to perform query requests now or is there any consideration to implement a query language feature?
I am figuring out a way to get modules and/or devices that are currently connected to your edgeHub. For that, I am doing GetModuleTwin requests for each device. With a query, I could save some requests.
If there is no implementation for that and it is considered a useful feature, I would like to develop it :)
I am using this code.
iotClient, err := iotdevice.NewFromConnectionString(mqtt.New(), connString)
if err != nil {
fmt.Println(err.Error())
}
I got this error
network Error : x509: certificate signed by unknown authority
Hello,
I've noticed that the ModuleTransport does not allow for MQTT over websocket like Transport implementation in the mqtt package:
iothub/iotdevice/transport/mqtt/mqtt.go
Line 130 in 0dae904
Was that intentional? If there was no reason, like it's not supported, I would add it to ModuleTransport.
I have an application that was working since September 2020, but on January 13 it stopped :/
I am trying to 'SubscribeEvents' and I get this error "x509: certificate signed by unknown authority".
I can connect IoTservice.NewFromConnectionString(MyConnectionString), but when I subscribe to events I got this error.
I would appreciate any help to configure it and make it works again.
I found some events not received using SubscribeEvents() whitch may be consumer group race conditions.
How to fix this problem?
Many thanks.
As title mentioned.
Thank you for the helping
Hi.
In our project I've faced with 30s timeout when twin updates. But in transport setting and context I set 2 minutes. I've found the following code. Is it really need this constant timeout?
Azure, and specifically IOT Central, supports numbers/strings/bools as direct method payloads, which don't work in the marshaling layer here. One option would be to defer marshal/unmarshal to the implementer of the DirectMethodFunc. Maybe this could be handled in conjunction with #23 - there could be something like RegisterDirectMethodAdvanced
that support asynchrony and did bytes in, bytes out.
My device is setup using a Device Provisioning Service. I'm using ModuleClient to register a direct method. We get the auth from NewModuleFromEnvironment
On callback I get the following error while parsing the request id
ERROR parse error: $rid parse error: strconv.ParseInt: parsing "327358f7-b968-4f62-a8e5-8fb0cda316d4": invalid syntax.
When using the NewModuleFromConnectionString everything works fine so far. Any pointers on how to fix this with the DPS?
https://github.com/amenzhinsky/iothub/blob/v0.7.0/common/tls.go
Digi root G2 root cert is not included, latest IoT hub are migrated from Baltimore to Digi G2 root
The TODO section mentions that the APIs are still not finalized.
I found an interface in the official Azure Go SDK which might help you in finalizing the APIs.
Take a look at this interface.
EDIT:
Although, there are no APIs specific for devices. But we can reuse the Naming Convention and several keywords which are used throughout Azure SDK.
I have an issue where I'm unable to publish events. Unfortunately I can't identify any more related circumstances than that. It has occurred some times, but in most cases it works as expected.
In essence the code works as follows:
ctx := context.Background()
message := []byte("Hello, World!")
expiry := 10 *60 * time.Second
deviceId := "some-device"
if err := client.SendEvent(
ctx,
deviceId,
message,
iotservice.WithSendAck((iotservice.AckType)("full")),
iotservice.WithSendExpiryTime(time.Now().Add(expiry)),
); err != nil {
return err
}
The error is the following:
link detached, reason: *Error{Condition: amqp:link:detach-forced, Description: Server Busy. Please retry operation, Info: map[]}
The Java SDK seems to have this comment regarding the error:
/**
* An operator intervened to detach for some reason.
*/
LINK_DETACH_FORCED("amqp:link:detach-forced"),
Same with the JS one: https://github.com/Azure/amqp-common-js/blob/master/lib/errors.ts#L171.
So to me it seems as if this error may occur from time to time. For me, it has always been solved with a restart, so I assume one way to handle it is to simply reconnect the client.
Is this possible to do in code, and if so are there plans to add this feature?
I am trying to avoid having to manually register every IOT device with azure.
Go Version: go 1.19
ERROR message parse error: invalid semicolon separator in query
c, err := iotdevice.NewFromConnectionString(
iotmqtt.New(), "xxxxxxxxxxxxxxxxxxxx",
)
if err != nil {
log.Fatal(err)
}
// connect to the iothub
if err = c.Connect(context.Background()); err != nil {
log.Fatal(err)
}
// receive a cloud-to-device message
eventSub, err := c.SubscribeEvents(context.Background())
for msg := range eventSub.C() {
fmt.Println(msg.Payload)
}
if err := eventSub.Err(); err != nil {
log.Fatal(err)
}
panic: interface conversion: interface {} is nil, not string
goroutine 204 [running]:
github.com/amenzhinsky/iothub/common/commonamqp.FromAMQPMessage(0xc00040cbd0, 0x1)
/tmp/iothub/common/commonamqp/message.go:51 +0xa68
github.com/amenzhinsky/iothub/iotservice.(*Client).SubscribeEvents.func1(0xc00040cbd0)
/tmp/iothub/iotservice/client.go:208 +0x34
created by github.com/amenzhinsky/iothub/eventhub.SubscribePartitions
/tmp/iothub/eventhub/client.go:132 +0x1e9
This is because the value is not a string
:
https://github.com/amenzhinsky/iothub/blob/master/common/commonamqp/message.go#L46
fmt.Printf("%+v", msg.ApplicationProperties)
returns:
map[stats:<nil>]
This happends when a device sends a message (using MQTT) to the following topic: devices/DEVICEID/messages/events/stats
I was trying to send a message from a device running a module (my go application) to the IoT hub.
On the device I have iotedge
installed in version 1.0.10.1
. At first I tried to build the sample, provided in the readme:
package main
import (
"context"
"log"
"os"
"github.com/amenzhinsky/iothub/iotdevice"
iotmqtt "github.com/amenzhinsky/iothub/iotdevice/transport/mqtt"
)
func main() {
c, err := iotdevice.NewFromConnectionString(
iotmqtt.New(), os.Getenv("IOTHUB_DEVICE_CONNECTION_STRING"),
)
if err != nil {
log.Fatal(err)
}
// connect to the iothub
if err = c.Connect(context.Background()); err != nil {
log.Fatal(err)
}
// send a device-to-cloud message
if err = c.SendEvent(context.Background(), []byte(`hello`)); err != nil {
log.Fatal(err)
}
}
Even though this works, it requires me to add the connection string to the environment variables of my container. On a container for a module I have the following environment variables set:
After looking a bit around in the code, I found the function NewModuleFromEnvironment()
which is reading most of those variables, so I adjusted the code and tried to run it:
package main
import (
"context"
"log"
"github.com/amenzhinsky/iothub/iotdevice"
iotmqtt "github.com/amenzhinsky/iothub/iotdevice/transport/mqtt"
)
func main() {
c, err := iotdevice.NewModuleFromEnvironment(
iotmqtt.New(),
false,
)
if err != nil {
log.Fatal(err)
}
// connect to the iothub
if err = c.Connect(context.Background()); err != nil {
log.Fatal(err)
}
// send a device-to-cloud message
if err = c.SendEvent(context.Background(), []byte(`hello`)); err != nil {
log.Fatal(err)
}
}
My application always exits with:
2020/11/12 15:15:02 Connect:
2020/11/12 15:15:02 not Authorized
And I'm unable to tell what part is missing. Other packages can send messages quite fine when only the selected settings are provided. Any idea how I can send a message from a Go module without providing the connection string to each and every module?
The Accept
method of Azure/go-amqp
has been renamed to AcceptMessage
in v0.16.1. Commit where it happened: Azure/go-amqp@760a290.
Issue that is raised when installing iothub
when already having a newer version of Azure/go-amqp
installed:
# github.com/amenzhinsky/iothub/eventhub
github.com/amenzhinsky/[email protected]/eventhub/client.go:266:15: msg.Accept undefined (type *amqp.Message has no field or method Accept)
I'm looking to contribute support for IoT Hub file uploads as per https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-file-upload. The purpose of this issue is to signal that it's being worked on and to get the conversation going regarding the feature.
Is there anything I should take into account when implementing the feature?
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.