Code Monkey home page Code Monkey logo

terkin-datalogger's Introduction

Terkin

https://assets.okfn.org/images/ok_buttons/ok_80x15_red_green.png https://assets.okfn.org/images/ok_buttons/oc_80x15_blue.png https://assets.okfn.org/images/ok_buttons/os_80x15_orange_grey.png

Chart recorder

Data logging for humans, written in MicroPython.



At a glance

Terkin is a flexible data logger application for MicroPython and CPython environments. It provides a lot of sensor-, networking- and telemetry-connectivity options.

Terkin has been conceived for the Bee Observer (BOB) and Hiveeyes projects and was funded by the BMBF.

Features

Batteries included.

Overview

  • Modular firmware framework
  • Flexible configuration settings subsystem
  • Compatible with MicroPython and CPython
  • Concise, readable and modularized code which is easy to follow
  • Decoupled code domains and data models for sensors vs. telemetry
  • Based on approved modules from the MicroPython standard library
  • Convenient development sandbox and test suite for quick iteration cycles

Architecture

  • Datalogger and Device are singleton objects representing the data logger application and the logging device.
  • Components of the sensor subsystem wrap hardware drivers to generalize sensor reading.
  • The telemetry subsystem uses different transport adapters to implement various connectivity scenarios.

Hardware support

Architectures

  • x86_64, ARM, ESP32, STM32

Platforms

  • Genuine MicroPython: PYBOARD-D, TTGO T-Call, TTGO T-Beam
  • Pycom MicroPython: WiPy, GPy, LoPy4, FiPy
  • CPython: Linux x86_64, Linux ARM (BeagleBone, Odroid, Raspberry Pi), macOS, WSL

Peripherals

  • Sensors: 1-Wire, I2C, ADC, System, WiFi
  • Drivers: DS18B20, BME280, BMP280, SI7021, ADS1x15, HX711, MAX17043, DS3231, AT24C32, INA219
  • Adapters: GPSD, EPSolar ViewStar PWM charge controller, Victron Energy VE.Direct MPPT charge controller, Raspberry Pi USV+
  • Connectivity: WiFi, SIM800 for GPRS, SX127x for LoRa (LoPy4, FiPy and Dragino LoRa/GPS HAT), Sequans Monarch for LTE Cat M1 or LTE Cat NB1
  • Telemetry: WiFi/MQTT, WiFi/HTTP, SIM800/HTTP, LoRaWAN/TTN OTAA+ABP

Screenshots

Sensorkit and board

https://ptrace.hiveeyes.org/2019-06-17_bob-sensorkit-small.jpeg

Bee Observer Sensorkit, assembled.

https://ptrace.hiveeyes.org/2019-06-17_bob-board-small.jpeg

Bee Observer Board, assembled.

Console output

To get a better idea about how running this firmware will feel like when watching its log output, we collected some excerpts at Running the Terkin Datalogger.

Getting started

Introduction

See Getting started with the Terkin Datalogger.

The documentation covers the main features of the MicroPython datalogger firmware and walks you through the setup process of the development sandbox environment in detail.

If you feel you have questions about anything you might encounter during the setup and installation process or if you even have suggestions to improve things, feel free to get back to us by creating an issue on the GitHub repository.

Download

On the release page , bundles of the most recent software versions are available through .tar.gz and .zip archives. These are suitable for uploading through Pymakr or similar development environments / file synchronization tools.

Configuration

Copy the settings.example.py blueprint into settings.py and adjust each configuration setting appropriately. The documentation of all parameters is in the file itself and should be reasonably self-explanatory. For using Terkin with TTN/LoRaWAN find some more details here.

Sandbox setup

If you would like to contribute to the development or want to setup a development environment for running the head version of this software, please follow up at Setup Terkin Datalogger sandbox to read about how to install the MicroPython firmware development environment and other software components it relies on and how to configure it properly.

The programming environment is command line based and has been tested successfully on Linux, macOS and the Windows Subsystem for Linux (WSL) shipped with Windows 10.

Acknowledgements

This software is an effort of many people. Thanks to all the contributors who helped to co-create and conceive it in one way or another. You know who you are.

License

This project is licensed under the terms of the AGPL license.


Have fun!

terkin-datalogger's People

Contributors

amotl avatar clemensgruber avatar einsiedlerkrebs avatar mko1640 avatar nznobody avatar poesel avatar rohlan avatar thiasb avatar wst89 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  avatar  avatar  avatar  avatar

terkin-datalogger's Issues

Tests: Upgrading to `mocket-3.10.x`

Hi there,

for emulating access to a fake socket.AF_LORA socket, we are currently using a highly modified version of mocket.Mocket/mocket.MocketEntry within a custom LoRaNetworkFixture.

class LoRaNetworkFixture:
LORA_RESPONSE_PORT = 0
def __init__(self):
self.monkeypatch = MonkeyPatch()
import network
self.monkeypatch.setattr('network.LoRa', LoRaMock(), raising=False)
import socket
self.monkeypatch.setattr('socket.AF_LORA', socket.AF_INET, raising=False)
self.monkeypatch.setattr('socket.SOCK_RAW', socket.SOCK_STREAM, raising=False)
self.monkeypatch.setattr('socket.socket', MocketSocket, raising=False)
def register_conversation(self, response_port=None, response_data=None):
from mocket import Mocket, MocketEntry
response_port = response_port or self.LORA_RESPONSE_PORT
response_data = response_data or []
lora_entry = MocketEntry(location=(None, None), responses=response_data)
lora_entry.request_cls = bytearray
Mocket.reset()
Mocket.register(lora_entry)
def recvfrom(self, buffersize):
if response_data:
buffer = self.recv(buffersize)
return buffer, response_port
else:
return None, response_port
self.monkeypatch.setattr(MocketSocket, 'recvfrom', recvfrom, raising=False)
def shutdown(self):
self.monkeypatch.undo()

Those modifications are currently not compatible with the most recent mocket-3.10.x release series, so we will have to come up with a solution. It still works on mocket-3.9.x (modulo Python 3.11, but for different reasons).

With kind regards,
Andreas.

References

Installing pycopy-cpython-upip through pip fails

We recently found [1]ย that installing pycopy-cpython-upip fails, probably after the release 1.3.1 from Aug 11, 2019.

$ pip install pycopy-cpython-upip
Collecting pycopy-cpython-upip
  Using cached https://files.pythonhosted.org/packages/e0/ac/5ddaf3cd3f86af13702721fff23623fa51382c0ac47082482a74d22f54e1/pycopy-cpython-upip-1.3.1.tar.gz
Collecting pycopy-cpython-cpython-uerrno (from pycopy-cpython-upip)
  ERROR: Could not find a version that satisfies the requirement pycopy-cpython-cpython-uerrno (from pycopy-cpython-upip) (from versions: none)
ERROR: No matching distribution found for pycopy-cpython-cpython-uerrno (from pycopy-cpython-upip)

[1] https://community.hiveeyes.org/t/hilfe-beim-setup-der-terkin-datenlogger-sandbox/2501

Problems invoking "make" on WSL

Running 'make' on Win10 produces a slightly weird output.
First there are two faults from 'sh'.
Second: in the rules the first character is strangely missing.

markus@Calvin:/mnt/c/Users/Markus/Documents/GitHub/hiveeyes-micropython-firmware$ make
/bin/sh: 1: test: Linux: unexpected operator
Available rules:

 etup                         Prepare sandbox environment and download requirements
 erkin-agent                  Run the MicroTerkin Agent, e.g. "make terkin-agent action=maintain"
 rovide-wifi                  Load the MiniNet module to the device and start a WiFi access point.
 onnect-wifi                  Load the MiniNet module to the device and start a WiFi STA connection.
 p-address                    Load the MiniNet module to the device and get IP address.
 py-compile                   Compile all library files using mpy-cross
 ecycle                       Upload framework, program and settings and restart attached to REPL
 ecycle-ng                    Upload framework, program and settings and restart device
 ketch-and-run                Upload program and settings and restart attached to REPL
 yboard-install               Pyboard-D transfer
 nstall                       Install all files to the device, using rshell
 nstall-ftp                   Install all files to the device, using FTP
 nstall-rshell                Install all files to the device, using USB (rshell)
 nstall-ng                    Install all files to the device, using best method
 name                         Display operating system information
 rint-%                       It allows you to quickly get the value of any makefile variable, e.g. "make print-MCU_PORT"
 estart-device-http           Restart device using the HTTP API
 hip_id                       Display chip_id
 nstall-pycom-firmware        Install Pycom firmware on device
 ormat-flash                  Format flash filesystem with LittleFS
 rase-fs                      Erase flash filesystem
 rase-device                  Erase flash filesystem
 repare-modem-upgrade         Put Sequans modem into recovery mode to prepare firmware upgrade
 ist-serials                  List all serial interfaces
 eset-port                    Reset port or even USB subsystem
 ist-boards                   List all MicroPython boards
 evice-info                   Inquire device information
 onsole                       Open console over serial or telnet
 shell                        Run interactive rshell on the device
 epl                          Run interactive REPL on the device
 eset-device                  Send reset command to device
 eset-device-attached         Send reset command to device and keep the REPL shell attached

/bin/sh: 2: @#: not found
Documentation:

Please check https://community.hiveeyes.org/t/operate-the-terkin-datalogger-sandbox/2332
in order to get an idea how to operate this software sandbox.

Have fun!

Add support for Sensirion SHT31 sensor devices

Hi there,

at [1], we are discussing the current shortage on Bosch's BME and BMP sensors, started by @ClemensGruber. @wetterfrosch also stated at [2]:

Meteorologically, I've also been squinting more at Sensirion's SHTxx series lately.

In order to implement this for Terkin, we might want to look at those libraries by @mcauser and @kfricke:

With kind regards,
Andreas.

[1] https://community.hiveeyes.org/t/bme280-lieferengpass/4176
[2] https://community.hiveeyes.org/t/bme280-lieferengpass/4176/9

MicroPython support for ADS123x

As many discussions on https://community.hiveeyes.org/ about the HX711 [1] show, the community is also looking at supporting the ADS123x 24-Bit, Delta-Sigma ADC [2] series for reading the weight scale.

While looking for a solid C library on [3], we found the ADS123X library [4] by @HamidSaffari to be the best one available so far. So, porting this to the ESP-IDF (RTOS) and wrapping it from MicroPython would definitively be nice.

[1] https://community.hiveeyes.org/search?q=hx711
[2] https://community.hiveeyes.org/search?q=ads123
[3] https://community.hiveeyes.org/t/code-fur-ads1232-oder-ads1234/392
[4] https://github.com/HamidSaffari/ADS123X

Running "make setup" inside Docker

Dear @poesel,

after merging your update from #19 to the check-program rule, make setup stopped working but croaked at us:

make setup
ERROR: "virtualenv" program not installed.
HINT: Install on Debian-based systems using 'apt install python-virtualenv python3-virtualenv' or use the package manager of your choice
make[1]: *** [check-program] Error 1
make: *** [check-virtualenv] Error 2

We reverted this amendment by 2980460. Please feel free to get back to us here in order to sort things out. Why that would not work on your machine is currently beyond my imagination. May I humbly ask on which environment you are running this?

With kind regards,
Andreas.

Implement deep sleep between readings

People have been asking [1,2] for Deep Sleep on FiPy. While @einsiedlerkrebs and others [3] tried already,

I have tried deep sleep, but without success. Tried 1s sleep but device didn't wake up.

I have to admit that I did not do any debugging on this yet.
see: hiveeyes/hiveeyes-micropython-firmware@191a46b

it looks like it would be harder than expected. As usual, the devil might just be in the details.

We will try to dedicate some time to that exceptionally important detail.

[1] https://community.hiveeyes.org/t/bob-platine-im-lipo-betrieb-ohne-deep-sleep/2055
[2] Hiverize/FiPy#2
[3] pycom/pycom-libraries#26

Add support for NAU7802

We would like to add support for the Nuvoton NAU7802 24bit 2ch AFE for bridge sensors by @OpenNuvoton.

@ClemensGruber just found the nau7802py implementation by @longapalooza, who ported the SparkFun Qwiic Scale Arduino library by @nseidle to CPython. Thanks, Will! Edit: I also just disovered another CPython implementation PyNAU7802 by @BrunoB81HK. Thanks, Bruno!

Now, we are aiming to support this within Terkin on
a) RaspberryPi by using the nau7802.c Linux IIO implementation.
b) MicroPython by adding appropriate support through the nau7802py implementation, which will have to be adjusted for MicroPython.

Add HX711 section in settings.example.py

There is no HX711 for the load cell specified in settings.example.py, we should add e.g.

'registry': {
    'hx711': {
        'pin_dout': 'P22',
        'pin_pdsck': 'P21',
        'scale': 11.026667,
        'offset': 130800.0,
    },

Better support for VSC

Now that all code is in /src there is only one step missing to be able to seamlessly use VSC: either move dist-packages into src (there is probaly some thelogical reason not to do this ;) ) or just link the directory into src (that is what I do).

Add support for MAX17043

Hi there,

as mentioned within #30 (comment), @poesel wants to support the MAX17043 within this datalogger.

While we appreciate that in general, we just had a look at DFRobot_MAX17043.py and would like to mention that having these guys on the module level will not be feasible:

# Get I2C bus
i2c = I2C(scl = Pin(22), sda = Pin(21), freq=400000)

So, we will have to amend that lowlevel driver accordingly.

With kind regards,
Andreas.

Appropriately compute "time_until_next_measurement"

Support displays

Add telemetry support for cellular networks (GPRS)

TinyGSM integration

The coolest thing would be to support quite a bunch of GSM/GPRS modules by bringing the excellent TinyGSM library to the world of MicroPython. We mentioned that idea within stinos/micropython-wrap#3 and vshymanskyy/TinyGSM#362 the other day.

Pure-Python SIM800 support

On the other hand, using a specialized pure-Python module for supporting the SIM800 module at least is probably one of the more lower hanging fruits right now. We researched this topic already [1] and now found the implementation by @sarusso to be the most promising to integrate - thanks a bunch! We will further discuss that topic on our forum [2].

[1] https://community.hiveeyes.org/t/micropython-libs-for-sim800-on-esp32/1492
[2] https://community.hiveeyes.org/t/unlocking-and-improving-the-pythings-sim800-gprs-module-for-micropython/2978


cc @poesel, @thiasB, @ClemensGruber

Getting WiFi auth mode fails on hidden networks

Hi there,

alongside the other contributions already landed and in-flux, @nznobody has another interesting gem in his tmp/dev tree:

Getting auth mode fails on hidden networks as they do not show in the scan. c2efc2e makes not detecting authmode not a critical error.

We probably should integrate this patch into mainline.

With kind regards,
Andreas.

CPython: Feature to create configuration file blueprint

Hi there,

@MKO1640 reported at [1] that he had troubles creating or getting hold of an appropriate configuration file blueprint for getting started quickly after installing Terkin on CPython using pip.

In order to improve that situation, it would be nice if Terkin would provide a corresponding subcommand like terkin create-config --flavor=sbc, which would print a configuration file blueprint to STDOUT. That can be redirected into a configuration file of your choice.

So, the workflow for installing Terkin on CPython would be:

pip install terkin[sbc] --upgrade
terkin create-config --flavor=sbc > settings.py
terkin --config settings.py

With kind regards,
Andreas.

[1] https://community.hiveeyes.org/t/terkin-variante-fur-cpython-raspberry-pi-dragino-lora-gps-hat/3180/9

Review implementations of system sensors for Pycom MicroPython

We just completed reasonable implementations of the most important system sensors we have been aiming at. They acquire runtime metrics from the ESP32 MCU and the FreeRTOS system running the Pycom MicroPython firmware. In general, this is about system uptime, WiFi reception and last but not least package temperature, free memory and battery level.

Especially, we would like to thank @ayoy for the Pycom MicroPython implementation of system.voltage we have been able to reuse from [1].

You can find these sensor implementations called SystemMemoryFree, SystemTemperature, SystemUptime and SystemBatteryLevel nicely wrapped up for reuse inside the terkin.sensor.system module.

As a picture says a thousand words, here is an example telemetry payload message yielded from these sensors.

{
  "system.time": 921,
  "system.runtime": 16,
  "system.uptime": 4202.907,
  "system.temperature": 41.30435,
  "system.memfree": 2262960,
  "system.voltage": 4.322425,
  "system.wifi.bandwidth": 2,
  "system.wifi.channel": 3,
  "system.wifi.country": "DE",
  "system.wifi.max_tx_power": 78,
  "system.wifi.rssi": -56
}

[1] https://github.com/ayoy/upython-aq-monitor/blob/lora/lib/adc.py

Some LoRaWAN tests are failing

@thiasB just reported that two LoRaWAN tests will fail. He is using Linux Mint 19.3 and Python 3.8.

FAILED test/test_lorawan.py::test_uplink_system_temperature

E       AssertionError: assert bytearray(b'\x00\x01\x00') == bytearray(b'\...\x00\x01\x00')
E         At index 1 diff: 1 != 103
E         Right contains 4 more items, first extra item: 191
E         Full diff:
E         - bytearray(b'\x00g\x01\xbf\x00\x01\x00')
E         ?                 -------------
E         + bytearray(b'\x00\x01\x00')
FAILED test/test_lorawan.py::test_uplink_environmental_sensors

E       AssertionError: assert bytearray(b'\...\x00\x01\x00') == bytearray(b'\...\x00\x01\x00')
E         At index 0 diff: 10 != 0
E         Right contains 8 more items, first extra item: 41
E         Full diff:
E         + bytearray(b'\ng\x01\xe1\x0bg\x01\xe1\x00g\x00\x97\x00s)7\x00h\x9b\x00\x01\x00',
E         - bytearray(b'\x00g\x01\xbf\x00\x02\x01\x80\x01g\x01\xe1\x02g\x01\xe1'
E         -           b'\x03g\x00\x97\x00s)7\x00h\x9b\x00\x01\x00',
E           )

Support more sensors

Add support for Linux IIO sensor devices

Hi there,

at [1], started by @MKO1640, we are discussing what it would take to support reading measurement values from Linux IIO devices. @MKO1640 unlocked the hardware drivers for the HX711, NAU7802 and BME280 sensors the other day (thanks!).

At [2], there are some example implementations. Those snippets can be used for conceiving a sensor adapter for Terkin.

With kind regards,
Andreas.

[1] https://community.hiveeyes.org/t/erschliessung-des-hx711-treibers-fur-linux-iio/4091
[2] https://github.com/analogdevicesinc/libiio/tree/master/bindings/python/examples

CPython/Dragino: LoRaWAN implementation croaks with PyCryptodome

Hi there,

@tonke reported that the Dragino LoRaWAN implementation [1] croaks when using PyCryptodome, the successor library to PyCrypto.

>>> from Crypto.Cipher import AES
>>> AES.new(b"foobar")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: new() missing 1 required positional argument: 'mode'

Thank you!

Background: We switched to this library because it offers an almost drop-in replacement for the old PyCrypto library [2,3,4], which does not receive updates and is told to have some security flaws. See also #99 and #101.

With kind regards,
Andreas.

[1] https://github.com/daq-tools/dragino/tree/terkin/dragino/LoRaWAN
[2] https://www.dlitz.net/software/pycrypto/
[3] https://pypi.org/project/pycrypto/
[4] https://github.com/pycrypto/pycrypto

Some test issues

'make' listet die tests nicht auf

FYI:

test/test_gprs_http.py::test_telemetry_gprs_http FAILED                  [ 21%]
test/test_lorawan.py::test_uplink_environmental_sensors FAILED           [ 31%]
test/test_sensors.py::test_sensors FAILED                                [ 57%]

Der Rest geht durch.

Add pymakr.conf

Dear @ClemensGruber,

just recently, I found an extensive blueprint for a pymakr.conf settings file at [1]. It would be cool if we could include a respective file here which reflects the directory layout.

As you know already that I'm a command line guy, I am humbly asking if you could dedicate yourself to this.

Thanks already and with kind regards,
Andreas.

[1] https://forum.pycom.io/topic/5360/fipy-v1-2-problems


Global settings

{
	"address": "COM15",
	"username": "micro",
	"password": "python",
	"sync_folder": "",
	"open_on_start": true,
	"safe_boot_on_upload": false,
	"py_ignore": [
		"pymakr.conf",
		".vscode",
		".gitignore",
		".git",
		"project.pymakr",
		"env",
		"venv"
	],
	"fast_upload": false,
	"sync_file_types": "py,txt,log,json,xml,html,js,css,mpy",
	"ctrl_c_on_connect": false,
	"sync_all_file_types": false,
	"auto_connect": false,
	"autoconnect_comport_manufacturers": [
		"Pycom",
		"Pycom Ltd.",
		"FTDI",
		"Microsoft",
		"Microchip Technology, Inc."
	]
}

Project settings

{
    "address": "COM15",
    "username": "micro",
    "password": "python",
    "sync_folder": "",
    "open_on_start": true,
    "safe_boot_on_upload": false,
    "py_ignore": [
        "pymakr.conf",
        ".vscode",
        ".gitignore",
        ".git",
        "project.pymakr",
        "env",
        "venv"
    ],
    "fast_upload": false
}

'socket' object has no attribute 'flush' on ESP32

   36.8010 [terkin.telemetry            ] ERROR  : Connecting to MQTT broker at swarm.hiveeyes.org failed: AttributeError: 'socket' object has no attribute 'flush'
Traceback (most recent call last):
  File "/lib/terkin/telemetry.py", line 684, in connect
  File "/dist-packages/umqtt.py", line 100, in connect
AttributeError: 'socket' object has no attribute 'flush'

I could of course just comment this out for ESP32 but I'm not sure what it exactly does and if its important.

CPython: Run Terkin as system service

Hi there,

in order to support @MKO1640 in his endeavor to run Terkin on a Raspberry Pi Zero W device [1], it would be sweet if Terkin could also install itself as a system service by providing a corresponding systemd unit file, in turn invoking terkin --damon ....

The patch hiveeyes/saraswati#11 can be used as a blueprint for this improvement. The corresponding setup command for Terkin would then be sudo terkin setup --systemd.

With kind regards,
Andreas.

[1] https://community.hiveeyes.org/t/developing-terkin-for-cpython/4088/6

Improve telemetry field name mapping

Introduction

Together with @MKO1640, we are considering to use the ESP32-e-Paper-Weather-Display by @G6EJD (thanks, David!) in order to display beehive information on a Waveshare e-Paper module [1].

We outlined how to use Arduino to acquire beehive data from the HTTP data export interface of Kotori at [2].

Objective

Therefore, we would like to improve the telemetry field name mapping for MQTT and HTTP not to yield the technical sensor names like temperature.0x77.i2c:0, humidity.0x77.i2c:0 or temperature.28ff641d8fc3944f.onewire:0.

This has already been done for the field name mapping for the Bee Observer project, so we would like to generalize that to the other telemetry adapters.


[1] https://community.hiveeyes.org/t/anzeige-der-daten-auf-einem-e-paper-display/3229
[2] https://github.com/hiveeyes/hiveeyes-epaper-display/tree/master/lib/hiveeyes

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.