Code Monkey home page Code Monkey logo

meshtastic-map's Introduction

Meshtastic Map

discord twitter
donate on ko-fi donate bitcoin

A map of all Meshtastic nodes heard via MQTT.

My version of the map is available at https://meshtastic.liamcottle.net

How does it work?

  • An mqtt client is persistently connected to mqtt.meshtastic.org and subscribed to the msh/# topic.
  • All messages received are attempted to be decoded as ServiceEnvelope packets.
  • If a packet is encrypted, it attempts to decrypt it with the default AQ== key.
  • If a packet can't be decoded as a ServiceEnvelope, it is ignored.
  • NODEINFO_APP packets add a node to the database.
  • POSITION_APP packets update the position of a node in the database.
  • NEIGHBORINFO_APP packets log neighbours heard by a node to the database.
  • TELEMETRY_APP packets update battery and voltage metrics for a node in the database.
  • TRACEROUTE_APP packets log all trace routes performed by a node to the database.
  • MAP_REPORT_APP packets are stored in the database, but are not widely adopted, so are not used yet.
  • The database is a MySQL server, and a nodejs express server is running an API to serve data to the map interface.

Features

  • Connects to mqtt.meshtastic.org to collect nodes and metrics.
  • Shows nodes on the map if they have reported a valid position.
  • Search bar to find nodes by ID, Hex ID, Short Name and Long Name.
  • Hover over nodes on the map to see basic information and a preview image.
  • Click nodes on the map to show a sidebar with more info such as telemetry graphs and traceroutes.
  • Ability to share a direct link to a node. The map will auto navigate to it.
  • Device list. To see which hardware models are most popular.
  • Mobile optimised layout.
  • Settings available to hide nodes from the map if they haven't been updated in a while.
  • Real-Time message UI to view TEXT_MESSAGE_APP packets as they come in.
  • View position history of a node between a selectable time range.
  • "Neighbours" map layer. Shows blue connection lines between nodes that heard the other node.
    • This information is taken from the NEIGHBORINFO_APP.
    • Some neighbour lines are clearly wrong.
    • Meshtastic firmware older than v2.3.2 reports MQTT nodes as Neighbours.
    • This was fixed in meshtastic/firmware/#3457, but adoption will likely be slow...

TODO

  • use vuejs build process to make managing code easier
  • don't use cdn hosted javascript deps so we can run fully offline
    • offline map tiles?
  • dedupe packets to prevent spamming database

Install

Clone the project repo.

git clone https://github.com/liamcottle/meshtastic-map
cd meshtastic-map

Install NodeJS dependencies

npm install

Create a .env environment file.

touch .env

Add a database connection string for prisma to .env file.

DATABASE_URL="mysql://root@localhost:3306/meshtastic-map?connection_limit=100"

Note: Some queries are MySQL specific. Other db providers have not been tested.

Migrate the database.

npx prisma migrate dev

Run the MQTT listener, to save packets to database.

node src/mqtt.js

Run the Express Server, to serve the /api and Map UI.

node src/index.js
# Server running at http://127.0.0.1:8080

Note: You can also use a custom port with --port 8123

Upgrading

Run the following commands from inside the meshtastic-map repo.

# update repo
git fetch && git pull

# migrate database
npx prisma migrate dev

You will now need to restart the index.js and mqtt.js scripts.

MQTT Collector

By default, the MQTT Collector connects to the public Meshtastic MQTT server. Alternatively, you may provide the relevant options shown in the help section below to connect to your own MQTT server along with your own decryption keys.

node src/mqtt.js --help
Meshtastic MQTT Collector

  Collects and processes service envelopes from a Meshtastic MQTT server.

Options

  -h, --help                                    Display this usage guide.
  --mqtt-broker-url string                      MQTT Broker URL (e.g: mqtt://mqtt.meshtastic.org)
  --mqtt-username string                        MQTT Username (e.g: meshdev)
  --mqtt-password string                        MQTT Password (e.g: large4cats)
  --mqtt-topic                                  MQTT Topic to subscribe to (e.g: msh/#)
  --collect-service-envelopes                   This option will save all received service envelopes to the database.
  --collect-text-messages                       This option will save all received text messages to the database.
  --collect-waypoints                           This option will save all received waypoints to the database.
  --collect-neighbour-info                      This option will save all received neighbour infos to the database.
  --collect-map-reports                         This option will save all received map reports to the database.
  --decryption-keys <base64DecryptionKey> ...   Decryption keys encoded in base64 to use when decrypting service envelopes.
  --purge-interval-seconds number               How long to wait between each automatic database purge.
  --purge-nodes-unheard-for-seconds number      Nodes that haven't been heard from in this many seconds will be purged from the database.

To connect to your own MQTT server, you could do something like the following;

node src/mqtt.js --mqtt-broker-url mqtt://mqtt.example.com --mqtt-username username --mqtt-password password --decryption-keys 1PG7OiApB1nwvP+rz05pAQ==

MQTT Connection Status

The map shows a different coloured icon for nodes based on their connection state to MQTT.

  • Green: Online (connected to MQTT)
  • Blue: Offline (disconnected from MQTT)

This works by listening to /stat/!ID topics on the MQTT server.

When a node connects to MQTT it publishes online to the topic, and when the MQTT server detects the client has disconnected (via an LWT) it publishes offline to the topic.

The Meshtastic firmware configures an LWT (Last Will and Testament), which the MQTT server publishes upon client disconnect.

After a node boots up, there is a ~30 second delay before the online state is published. After a node disconnects from MQTT, there is a ~30 second delay before the offline state is published.

This works well when your node connects to MQTT over WiFi, however, when using the MQTT Client Proxy feature, your node sends/receives packets to/from your Android/iOS device, and then your device connects to MQTT and proxies the messages.

Meshtastic Node <-> Android/iOS <-> MQTT

Unfortunately, when using that feature your online / offline states will not work as expected.

As of the time of writing these docs, the mobile devices do not correctly configure the LWT for the node being proxied, and thus do not publish the offline state for the node, so you can't detect if your node disconnected from MQTT.

Your node will stay "stuck" in the online state in the MQTT server.

Docker Compose

A docker-compose.yml is available. You can run the following command to launch everything;

docker compose up

This will:

  • Start a MariaDB database server.
  • Run the database migrations.
  • Start the MQTT collector.
  • Start the Map UI.
  • Expose the map on port 8080.

Contributing

If you have a feature request, or find a bug, please open an issue here on GitHub.

License

MIT

Legal

This project is not affiliated with or endorsed by the Meshtastic project.

The Meshtastic logo is the trademark of Meshtastic LLC.

References

meshtastic-map's People

Contributors

billybag2 avatar fifieldt avatar gmart avatar ianmcorvidae avatar ketan avatar komelt avatar liamcottle 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

Watchers

 avatar  avatar  avatar  avatar  avatar

meshtastic-map's Issues

Docker/K8s implementation

Hey @liamcottle, first I wanted to say, awesome project! Was able to get it and running very quickly.

I'm working on a Docker compose implantation right now and had a few quick questions:

  1. Do mqtt.js and index.js need to interact with each other in any way? or is it all just mqtt.js writing to DB, and index.js displaying results? (trying to determine if they can be run as separate containers, and if so, any ports that need to be opened between them)

  2. Is there any interest in a PR to this repo for a Docker image built off of GitHub Actions and associated docs/docker-compose.yml?

  3. Same as 2, but a Kubernetes Helm chart

I understand the implications of merging 2 and 3 into this repo; it becomes 'official supported'. If thats something you're not ready to take on (I'm happy to help on an as-available), totally understand.

Either way, thanks for the awesome contribution to the Meshtastic ecosystem!

Remove waypoint

Hello Liam and readers
I created a waypoint about 4 weeks ago. It is still visible on the (your) map. But in Android I could not set an expiration date. Since then I wiped the data of the Meshtastic app and now I am not able to edit or delete this waypoint anymore. (ID 4292587809) Also I think there is no persistence MQTT entry for that. So now it only exists in your database or am I wrong ? Any idea how I could edit or delete this waypoint now ?
I am grateful for tips.

Some TextMessages are too long to store in the database

When running meshtastic-map for long enough, some exceptions like below arise.

Invalid prisma.textMessage.create()` invocation in
meshtastic-map/src/mqtt.js:312:42

309 }
310
311 try {
→ 312 await prisma.textMessage.create(
The provided value for the column is too long for the column's type. Column: text
at In.handleRequestError (meshtastic-map/node_modules/@prisma/client/runtime/library.js:122:6854)
at In.handleAndLogRequestError (meshtastic-map/node_modules/@prisma/client/runtime/library.js:122:6188)
at In.request (meshtastic-map/node_modules/@prisma/client/runtime/library.js:122:5896)
at async l (meshtastic-map/node_modules/@prisma/client/runtime/library.js:127:10871)
at async MqttClient. (meshtastic-map/src/mqtt.js:312:17) {
code: 'P2000',
clientVersion: '5.11.0',
meta: { modelName: 'TextMessage', column_name: 'text' }
`

Neighbors Displaying

Two new nodes: Heltec-V3 and WisBlock both with latest FW.

Heltec is Client with Neighbors turned On, WisBlock is Router_Client.

Heltec displays on Map but no Neighbors are showing up that saw Heltec or that Heltec saw.

Heltec connected to MQTT server, WisBlock is not.

Assumed Heltec would find WisBlock even with Neighbors off since both on FW 2.3+

No change even with WisBlock Neighbors On.

Great Map, BTW - good job!

UX - Collapse Neighbour Lines when to/from same destination

Having duplicate neighbor lines add's complexity to the view.
Can these be collapsed where the to/from are from the same nodes.

Example

image
image

Perhaps light blue where heard is only seen one way, dark blue where it's heard both ways, and the hover box shows both info boxes as one box showing both paths?

Alternatively when clicking on the line it splits the lines out into the green/red seen/heard colors? or green/red arrows on the end or similiar?

Remember status of Neighbors checkbox

When refreshing the page, the neighbors checkbox is always cleared for me. Not sure if it's a browser issue.

Also, if I select a node then select view neighbors, either "We heard" or "Heard us", then click the X to close that overlay, the neighbors are always unselected. The neighbors selection should go back to what was selected before.

FR - Node tracking

First of all, thank you for your great work. I'm a thankful every day user of the map.

Back in the day's I've looked at APRS. Some of the bigger APRS sites (https://aprs.fi) do offer tracking of a station automatically. This way you can backtrack where the node has been. It will plot its route by dots and colored lines.

Perhaps this could be a useful option to have on your map?

I do have some requests:

  1. For privacy reasons only look back for 1, 2 or 3 hours?
  2. Add info to the plotted dots who heard that node on that location. It will help to identify coverage issues.

Unable to decrypt AES256 key

If using a different randomally generated PSK for primary channel, meshtastic will generate an AES256/32 byte key.
mqtt.js is currently unable to decrypt this packet even when passed the key as an argument. I've added some debugging in - receiving a packet with an AES256 key throws the following:

RangeError: Invalid key length
at Decipheriv.createCipherBase (node:internal/crypto/cipher:121:19)
at Decipheriv.createCipherWithIV (node:internal/crypto/cipher:140:3)
at new Decipheriv (node:internal/crypto/cipher:289:3)
at Object.createDecipheriv (node:crypto:155:10)
at decrypt (/home/ro/meshtastic-map/src/mqtt.js:241:37)
at MqttClient. (/home/ro/meshtastic-map/src/mqtt.js:331:29) {
code: 'ERR_CRYPTO_INVALID_KEYLEN'
}

I'm not overly experienced with javascript, but I was able to solve the problem by adding the following:
if (decryptionKey.length == 24) {
// create aes-128-ctr decipher
decipher = crypto.createDecipheriv('aes-128-ctr', key, nonceBuffer);
} else {
// create aes-256-ctr decipher
decipher = crypto.createDecipheriv('aes-256-ctr', key, nonceBuffer);
}

I'd fork/pull, but I'm not sure that's the most efficient way to do it?

Decoupling mqtt listener and web ui

As I wrote in the issue title, it would be smart if the mqtt listener and web UI were decoupled. This is a draft of how it would look:

meshtastic-map
├── docker-compose.yaml
├── mqtt-listener
│  └── dockerfile
├── prisma
└── web
   └── dockerfile

I think it would be also a good time to add a proper vue js build process as you wrote in To Do.
And I think implementing Docker would help to easily self host the project.

Cleanup sidebar

Hi,
Ideas for the sidebar. I think it's getting really long, even on a 1440p screen.
Here is what I suggest:
The separator could be a line, comma, table, etc.

  • Put Long and short name into one row, with some sort of separator.
  • Put Role and firmware into one row
  • Put Region and frequency into one row
  • Put Modem Preset and Has Default into one row
  • Put Lat and Long into one row
  • Maybe? Battery level and volt into one row?
  • ID and Hex ID into one row
  • Neighbors updated and Position updated into one row.

Thanks

UX - Seen/Heard Color Legend (like MQTT Status Legend)

Can we get a legend in bottom left of the screen next to the MQTT status legend to describe the colours of the lines?

In example provided below, You can work out Red with Arrow pointing in is "We Heard", but what is the Green Line with no arrows?

image
image

Voltage typically 3.2-4.2v

The voltage graph min could be set to minimum of 3.2v and all measurements. The maximum to the maximum of 4.2 volts and all measurements. The voltage and battery % would look then look similar. (I don't think they follow each other in all cases and the relationship may not be linear)

At the time of writing min v looked to be 0v and the voltage graph was difficult to see as the line was only at the top.

Adding hopStart (added since 2.3) to database

Adding hopStart (added since 2.3) to database -> to have a better neighbor mapping. To make better graph even on node without neighbor info enabled.
Also neighbor info is a pain to collect because it's a low priority frame. With the hopLimit == hopStart trick we can get neighbor and RSSI/SNR from all message or any frames :) and make better link on map, or raw mesh graph like my one https://www.serveurperso.com small homepage made with D3.js showing live Parisian mesh network.
Sans titre

Node ID collisions

It's possible for two nodes to have an identical ID (while it means two colliding int32s, it definitely happens -- I have a 3000km link showing for me to a different node that shares an ID with one a few meters away). I'm not sure what the best way to fix this would be, but it would probably be good, if possible, to treat these as several different nodes.

In this case, I think it may be a specific bad ID -- https://meshtastic.liamcottle.net/?node_id=115503498 has multiple cross-country links. Nevertheless, these collisions do seem to happen and it'd improve the map if they could be intelligently detected, probably.

I do also understand this isn't a very easy problem to solve, but perhaps things can be partitioned by uplink/source node to some degree; when a given ID consistently gets similar names and positions from one uplink and gets a different similar set of names and positions from another uplink, the existence of a collision can maybe be inferred.

UX - Splt stacked nodes

When nodes are stacked on each other, as noted it's hard to click in to see a node beneath to see details, could calculate if they are within 10meters of each other for example?

Can tell there is more then one node here due to having "Heard" enabled on another node and you still have a blue line next to it.

image

An another mapping/visual tool I use - https://lukeprior.github.io/nbn-upgrade-map/?suburb=mount-barker&state=SA&commit=main

When there's more then one location there it displays a number on the dot, and when click spreads them out and can click on the individual marker for details.
image
image

Can't click on Show Full Details sometimes

Bug:

  1. Search for a node and select it
  2. It zooms into that node
  3. I'm unable to click on "Show Full Details"

If I click elsewhere, then click back on the node, I can click the button.

I tried on Edge and Chrome, it might work elsewhere?

Waypoint longitude shown wrong

Hello
Just a small issue in the pop-up of a waypoint: It seams that 360 is added to it. Position on the map is fine.

image

I grabbed the MQTT packet for that one:
{"channel":0,"from":3663091580,"hops_away":0,"id":4292587810,"payload":{"description":"Great viewpoint","expire":2147483647,"id":4292587809,"latitude_i":468602396,"locked_to":0,"longitude_i":75258635,"name":"Chutzen"},"sender":"!da56577c","timestamp":1711963987,"to":4294967295,"type":"position"}

Thanks for the great map ! I did not understand Neighbours yet.

See more than 24 hours of battery history in node details

For solar nodes, being able to track battery life over longer than a 24 hour period would be great.
I see that there are other issues about cleaning up the sidebar (e.g. #21 ), but having extended battery stats would be very beneficial for solar node operators due to weather affecting solar power collection.
Could the battery graph be dropped to a new line below battery level and percentage, in order to take up the whole side bar, instead of just the right column of it's row? Then you could show 48+ hours of data hopefully.

Offline node showing as constantly updated

Nodes that are offline for days still updating on the map somehow.

I have a node which is shut down on my shelf for 2 days, Android app off on my phone, yet the map says it's updated minutes ago.
Nothing changes, neither name/telemetry just the "Updated" line.

Much more route trace info is available passively.

If a node participates in a trace route then this is provide trace route info for that node.This also provides an incoming route, not just an outgoing one.

Also the route discovery packets contain passive route information. (Ignoring the destination)

So a routing layer on the map could be added to show incoming and outgoing routes to a selected node. Including routes the node participated in and route discovery that the node participated in, even if the route was not discovered.

Or a new layer could just display all hops deduced from any route data as a line.

some air_util_tx out of range for database schema

Received the error below:

PrismaClientKnownRequestError: 
Invalid `prisma.deviceMetric.create()` invocation in
src/mqtt.js:544:51

  541 
  542 // create metric if no duplicates found
  543 if(!existingDuplicateDeviceMetric){
→ 544     await prisma.deviceMetric.create(
Value out of range for the type. Out of range value for column 'air_util_tx' at row 1
    at In.handleRequestError (node_modules/@prisma/client/runtime/library.js:122:6854)
    at In.handleAndLogRequestError (node_modules/@prisma/client/runtime/library.js:122:6188)
    at In.request (node_modules/@prisma/client/runtime/library.js:122:5896)
    at async l (node_modules/@prisma/client/runtime/library.js:127:10871)
    at async MqttClient.<anonymous> (src/mqtt.js:544:25) {
  code: 'P2020',
  clientVersion: '5.11.0',
  meta: {
    modelName: 'DeviceMetric',
    details: "Out of range value for column 'air_util_tx' at row 1"
  }
}
PrismaClientKnownRequestError: 
Invalid `prisma.node.updateMany()` invocation in
src/mqtt.js:564:39

  561 // update node telemetry in db
  562 if(Object.keys(data).length > 0){
  563     try {
→ 564         await prisma.node.updateMany(
Value out of range for the type. Out of range value for column 'air_util_tx' at row 1
    at In.handleRequestError (node_modules/@prisma/client/runtime/library.js:122:6854)
    at In.handleAndLogRequestError (node_modules/@prisma/client/runtime/library.js:122:6188)
    at In.request (node_modules/@prisma/client/runtime/library.js:122:5896)
    at async l (node_modules/@prisma/client/runtime/library.js:127:10871)
    at async MqttClient.<anonymous> (src/mqtt.js:564:21) {
  code: 'P2020',
  clientVersion: '5.11.0',
  meta: {
    modelName: 'Node',
    details: "Out of range value for column 'air_util_tx' at row 1"
  }
}

Node TTL is way too long

Why does it store node data so long?

Do you have an opt out process?

Really is limited value to anything more than a day old.

feature request: view nodes by which gateways they heard by

I think it would would be interesting to be able to either filter or view nodes by the Uplink Node.

Essentially the ID in the original MQTT topic
msh/UK/2/c/LongFast/!da545934

Position messages on that topic, will just be the nodes that !da545934 is able to hear (even multi-hop).

While neighbours is good, and it is specifically the 'direct' neighbours. This would show the nodes, the particular node received, even multi-hop. which would show more of the mesh coverage. (although would only work if the Uplink node itself is publishing its position!)

Could be kinda like the 'Neighbours' buttons, and just draws a line to every node it has received a position report from.
Maybe Nodes we heard- analogous to the 'neighbours (we heard)' - ie it's not just direct neighbours, but all nodes heard.

Or maybe could be that enter a Node ID in search, and it would only show reports via that Node.


But could also have the reverse. Nodes heard us - shows all the Uplink nodes that have received a position packet from the node.

Ie see how far away a particular node has been heard - even multi-hop. (just with the limitation would only know about uplink nodes that heard the node, and again only if the upload node itself published its position)

(When I say 'Uplink Node' I think in the code they called the 'gateway' node. Saved packets store the gateway_id. So ultimately its viewing nodes by which gateway(s) they connected to)

[Feature Request]: Ability to reset Neighbor Overlay

I had the Neighbor Info Module enabled on a node I kept in my car. Nodes found while driving are not true direct neighbors when I get home. Can we have the ability to reset or have the Neighbor Info expire?

FR - display option for node Role

Greetings from an area with a growing mesh who are big fans of your work.

We currently have people cycling and hiking around the city to try and make their first connection to a node, and some dedicated people looking at planning links between well-positioned solar power nodes.

One feature that would be helpful to both of these types of users is a display option that limits the nodes displayed by node Role. As the number of nodes grow, we think having the option to choose to display, for example, just ROUTER / ROUTER_CLIENT and exclude CLIENT will give us a nice clean map so we can help the people who want to work with the more 'fixed'/'permanent' nodes.

Initial impressions was that this might best be done in the layers widget.

Open to any and all discussion :)

Remember selection of map options

Feature request:
Remember options like Neighbors shown, Waypoints, etc. on the map.
Currently those options are not persisted over website refreshes.

Route trace not being decoded correctly.

The route trace uses the packets to and from data in the header plus the payload that contains additional participants.
So a packet can be sent From: A To: Z asking for a route.
{ From:A To:Z }

Seen by B and "repeated".
{ From:A To:Z Route: [B] }

Seen by C and repeated.
{ From:A To:Z Route: [B,C] }

Seen by Z and so Z replies.
{ From:Z To:A Route: [B,C] }

Repeated without change. across the network.
{ From:Z To:A Route: [B,C] }

Seen by A.
So the route is an outgoing route. But this is a reply so the route starts with the "To" recipient, then the payload and ended at the from recipient.
(To)A [ B , C ] (From) Z
A->B->C->Z
This is called "2 Hops"
A -hop-> B -hop-> C -Rx-> z
So a 2 hop route has a payload of two nodes and a total of four participants in the route. (Plus a gateway node, which is typically just an observer.)
Very very confusing!

I think you are completely missing the "to" field. This is usually to "all", a special node ID.

Project rewrite

Hello, I am trying to contribute features to this project, but the Vue app, mqtt.js, and index.js are getting bigger and harder to read and understand. It would be smart to rewrite the front-end into a proper Vue app and use typescript for every aspect.

I am willing to help.

Remove extra image requests

The site currently requests about 2 dozen device images that don't exist each time the site loads. There could be a check to make sure the image exists before trying to load the image.
Or we can add a placeholder image for each device type.
I guess there already is one, but not sure how that is generated.

FR -- "claiming" a node and adding details

+1 for your "claim" feature in the readme

After claiming a node, an easy way to add more detail is by uploading the YAML config file, gotten via meshtastic --export-config.
This would parse the settings and provide details that can't be gotten via mesh transport, such as module configs.

neighbour information not updated/missing

Hi
Looks like the neighbour info (heard us / we heard) has not been updated for several days.
Is it disabled now ?
Will you consider enabling again ? Very usefull feature, on my point of view.

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.