GithubHelp home page GithubHelp logo

cossid / tasmotadevicegroupsforesphome Goto Github PK

View Code? Open in Web Editor NEW
11.0 11.0 4.0 81 KB

Tasmota device groups compatibility external component for ESPHome

License: GNU General Public License v3.0

Python 2.69% C++ 97.31%

tasmotadevicegroupsforesphome's People

Contributors

cossid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

tasmotadevicegroupsforesphome's Issues

Is this component working?

Hi, I've been trying to get this Tasmota group component to work with no luck. I installed 2 Wemos D1 mini boards with monochromatic led strips (PWM controlled MOSFETs) on opposite sides of a kitchen. I need these lights to mirror each other's other's state. Could you please take a look at my codes and let me know if I'm doing something wrong? I really appreciate any help at all. Thank you!

Primary Device Code
https://pastebin.com/UndJFtTC

Secondary Device Code
https://pastebin.com/Vz1cSM7g

build errors,

I got pointed out to this repo,

but When I try this I get builderrors

Compiling /data/keuken-aanrecht/.pioenvs/keuken-aanrecht/src/esphome/components/light/light_json_schema.cpp.o
In file included from src/esphome/components/device_groups/device_groups.cpp:1:
src/esphome/components/device_groups/device_groups.h:271:3: error: 'bools' does not name a type; did you mean 'bool'?
  271 |   bools DeviceGroupsStart();
      |   ^~~~~
      |   bool
src/esphome/components/device_groups/device_groups.cpp: In member function 'virtual void esphome::device_groups::device_groups::loop()':
src/esphome/components/device_groups/device_groups.cpp:140:9: error: 'DeviceGroupsStart' was not declared in this scope; did you mean 'DeviceGroupsStop'?
  140 |     if (DeviceGroupsStart()) {
      |         ^~~~~~~~~~~~~~~~~
      |         DeviceGroupsStop
src/esphome/components/device_groups/device_groups.cpp: At global scope:
src/esphome/components/device_groups/device_groups.cpp:211:6: error: no declaration matches 'bool esphome::device_groups::device_groups::DeviceGroupsStart()'
  211 | bool device_groups::DeviceGroupsStart() {
      |      ^~~~~~~~~~~~~
src/esphome/components/device_groups/device_groups.cpp:211:6: note: no functions named 'bool esphome::device_groups::device_groups::DeviceGroupsStart()'
In file included from src/esphome/components/device_groups/device_groups.cpp:1:
src/esphome/components/device_groups/device_groups.h:243:7: note: 'class esphome::device_groups::device_groups' defined here
  243 | class device_groups : public Component {
      |       ^~~~~~~~~~~~~
src/esphome/components/device_groups/device_groups.cpp: In member function 'bool esphome::device_groups::device_groups::_SendDeviceGroupMessage(int32_t, esphome::device_groups::DevGroupMessageType, ...)':
src/esphome/components/device_groups/device_groups.cpp:856:19: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
  856 |       while (item = *previous_message_ptr++) {
      |              ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
Compiling /data/keuken-aanrecht/.pioenvs/keuken-aanrecht/src/esphome/components/light/light_output.cpp.o
*** [/data/keuken-aanrecht/.pioenvs/keuken-aanrecht/src/esphome/components/device_groups/device_groups.cpp.o] Error 1
========================= [FAILED] Took 16.85 seconds =========================

on the esp8266 platform.

Sensors support?

Is it possible to send sensor states from one ESPHome node to another with this component?

Eg numeric, binary or text sensors. Grab them on the target node with template sensors.

ESP-IDF?

Does not currently work with ESP-IDF framework, as it lacks an arduino-compatible upd multicast library.

But how about just simply broadcast to the entire local network (send the udp packet to just 255.255.255.255)? Or maybe to a configurable target node only?

First control from different device than what's used until that point fails to propagate

I use device groups to sync my wall touch switches with light channels. The device groups work wonderfully most of the time. The only problem is when I try to switch OFF using a different device to the one I used to switch ON.

Ex:

  • Turn ON using wall switch, Light turns ON
    Turn OFF using wall switch, Lights turns OFF ✅

  • Turn ON using Light, Switch turns ON
    Turn OFF using Light, Switch turns OFF ✅

  • Turn ON using wall switch, Light goes ON
    Turn OFF using Light, Switch stays ON ❌
    Turn OFF light, Turn it back ON and then turn it OFF one more time, Switch turns OFF

  • Turn ON using Light, Switch turns ON
    Turn OFF using switch, Light stays ON ❌
    Turn OFF switch, Turn it back ON and then turn it OFF one more time, Light turns OFF

LibreTiny support

Hello,

apparently I'm the only one creating issues here.
But I was wondering if there will be LibreTiny support for this module.
I know you are aware of Libretiny as I saw your name popping up at some cloud cutter changes, so I skip the part where I explain what it does.
Right now when I apply device grouping on and RGBWW bulb with libretiny 1.2 or 1.4 device grouping looks ok.
but after changing the color temperature a couple of times the bulb goes dark/off until a restart. the interface does not know this and thinks everything is still ok. (this can also be an RGB issue I realize now)

my goal is to be able to use the wall mounted buttons to turn on/off the bulb.
As a workaround I now use automations but somehow for me the delay is always just to long.

individual relay identification

I don't think it is needed but if you need some discussion, or some testing this issue can be used.

Feel free to open a separate issue for individual relay identification inside the same group. I can't promise it will ever be resolved, but having discussion would be the first step.

I actually don't think this is needed, the only reason I tried this was because separate groups broke the config at the time.
never the less if you are planning to implement this you need some way to determine the index of the relays,

in config I can think about 2 things you can do

one is add an index(tasmota starts at one, but starting at 0 also could work)

device_groups:
  - group_name: "group1"         # Tasmota device group name
    switches:
      - id: gpio_switch          # ESPHome entity id
        index: 1
      - id: template_switch      # ESPHome entity id
        index: 3
    lights:
      - id: light_rgbww1         # ESPHome entity id
        index: 2

the other options is to put all relays in the same group, then determine the type dynamically

device_groups:
  - group_name: "group1"         # Tasmota device group name
    relays:
      - gpio_switch          # ESPHome entity id
      - light_rgbww1  
      - template_switch      # ESPHome entity id
             # ESPHome entity id

I have never developed a component for espHome before so I don't know if what I suggest would hit any limitation

Logging and Different devices

Hello,

me again,
I was checking some stuff and noticed something.
question 1. would it make sence to add this data to debug logging instead of verbose logging? I think this information is important enough and won't flood the logs as it only occurs on a change.

22:57:45	[V]	[dgr:586]	Received bulbkamervoor message from 192.168.180.173: seq=98, flags=16, 128=335544320*
22:57:45	[V]	[dgr:586]	Sending bulbkamervoor message to 192.168.180.173: seq=99, flags=8
22:57:45	[V]	[dgr:586]	Received bulbkamervoor message from 192.168.180.173: seq=99, flags=0, 5=184*
22:57:45	[V]	[dgr:586]	Received bulbkamervoor message from 192.168.180.173: seq=99, flags=0, 5=184*

The next thing is, I have 2 switches and 1 RGBWW light in the same group.
just so the switches can toggle the light, just for on/off, but this causes some strange behavior.
see the same log, a message get's received and instantly send back, and then received twice.

Not really sure what and how it happends, but sometimes the status doesn't update, probably because RGBWW had more/different data to send. for the light also, I only need to have the switch toggle in the group. is this somehow possible?

Device group for switch to lights not working after a few minutes (beken)

Hi, I'm not sure if this is configuration issue.
I have wall switch (N device) configured to control a light bulb (T device).
I noticed device group works for a few minutes after light bulb boots up, and then it doesn't seem to respond to the switch.

When it's working:

Wall switch log:

3:54:58 | [D] | [dgr:620] | Sending test3g message to 192.168.1.48: seq=231, flags=0, last ack=216
23:54:59 | [D] | [dgr:620] | Sending test3g message to 192.168.1.48: seq=231, flags=0, last ack=216
23:55:00 | [D] | [sensor:093] | '3g-beken Uptime': Sending state 7617.06787 s with 0 decimals of accuracy
23:55:00 | [D] | [dgr:620] | Sending test3g message to 192.168.1.48: seq=231, flags=0, last ack=216
23:55:01 | [D] | [button:010] | 'Internal light' Pressed.
23:55:01 | [D] | [light:036] | 'internal_light' Setting:
23:55:01 | [D] | [light:047] | State: OFF
23:55:01 | [D] | [light:085] | Transition length: 1.0s
23:55:01 | [D] | [dgr:620] | Sending test3g message to network: seq=232, flags=16, 5=255, 128=335544320
23:55:01 | [I] | [light:392] | 'internal_light' - Keeping current color mode Cold/warm white for call without color mode.
23:55:01 | [D] | [light:036] | 'internal_light' Setting:
23:55:01 | [D] | [light:051] | Brightness: 100%
23:55:01 | [D] | [light:085] | Transition length: 1.0s
23:55:01 | [D] | [light:036] | 'internal_light' Setting:
23:55:01 | [D] | [light:085] | Transition length: 1.0s
23:55:01 | [D] | [dgr:620] | Received test3g message from local: seq=232, flags=16, 5=255*, 128=335544320*
23:55:01 | [D] | [dgr:620] | Sending test3g message to network: seq=233, flags=0, 5=255
23:55:01 | [I] | [light:392] | 'internal_light' - Keeping current color mode Cold/warm white for call without color mode.
23:55:01 | [D] | [light:036] | 'internal_light' Setting:
23:55:01 | [D] | [light:051] | Brightness: 100%
23:55:01 | [D] | [light:085] | Transition length: 1.0s
23:55:01 | [D] | [dgr:620] | Received test3g message from local: seq=233, flags=0, 5=255*
23:55:01 | [W] | [component:214] | Component api took a long time for an operation (0.10 s).
23:55:01 | [W] | [component:215] | Components should block for at most 20-30ms.
23:55:01 | [D] | [dgr:620] | Received test3g message from 192.168.1.50: seq=233, flags=8
23:55:01 | [D] | [dgr:620] | Sending test3g message to network: seq=233, flags=0, 5=255
23:55:01 | [D] | [dgr:620] | Received test3g message from 192.168.1.50: seq=233, flags=8
23:55:01 | [D] | [dgr:620] | Sending test3g message to 192.168.1.13: seq=233, flags=0, last ack=231
23:55:01 | [D] | [dgr:620] | Sending test3g message to 192.168.1.48: seq=233, flags=0, last ack=216

Treatlife bulb:

<html>
23:54:56 | [D] | [dgr:620] | Received test3g message from 192.168.1.13: seq=26, flags=64
23:55:01 | [I] | [light:392] | 'treatlife-store' - Keeping current color mode Cold/warm white for call without color mode.
23:55:01 | [D] | [light:036] | 'treatlife-store' Setting:
23:55:01 | [D] | [light:051] | Brightness: 100%
23:55:01 | [D] | [light:085] | Transition length: 0.5s
23:55:01 | [D] | [light:036] | 'treatlife-store' Setting:
23:55:01 | [D] | [light:047] | State: OFF
23:55:01 | [D] | [light:085] | Transition length: 0.5s
23:55:01 | [D] | [dgr:620] | Received test3g message from 192.168.1.23: seq=232, flags=16, 5=255*, 128=335544320*
23:55:01 | [D] | [dgr:620] | Sending test3g message to 192.168.1.23: seq=233, flags=8
23:55:01 | [I] | [light:392] | 'treatlife-store' - Keeping current color mode Cold/warm white for call without color mode.
23:55:01 | [D] | [light:036] | 'treatlife-store' Setting:
23:55:01 | [D] | [light:051] | Brightness: 100%
23:55:01 | [D] | [light:085] | Transition length: 0.5s
23:55:01 | [D] | [dgr:620] | Received test3g message from 192.168.1.23: seq=233, flags=0, 5=255*
23:55:01 | [D] | [dgr:620] | Sending test3g message to 192.168.1.23: seq=233, flags=8
23:55:01 | [D] | [dgr:620] | Received test3g message from 192.168.1.23: seq=233, flags=0 (old)
23:55:44 | [D] | [dgr:620] | Sending test3g message to network: seq=2, flags=64

When it's not working:

Wall switch log

23:40:11 | [D] | [button:010] | 'Internal light' Pressed.
23:40:11 | [I] | [light:392] | 'internal_light' - Keeping current color mode Cold/warm white for call without color mode.
23:40:11 | [D] | [light:036] | 'internal_light' Setting:
23:40:11 | [D] | [light:047] | State: ON
23:40:11 | [D] | [light:085] | Transition length: 1.0s
23:40:11 | [D] | [dgr:620] | Sending test3g message to network: seq=176, flags=16, 128=335544321
23:40:11 | [I] | [light:392] | 'internal_light' - Keeping current color mode Cold/warm white for call without color mode.
23:40:11 | [D] | [light:036] | 'internal_light' Setting:
23:40:11 | [D] | [light:085] | Transition length: 1.0s
23:40:11 | [D] | [dgr:620] | Received test3g message from local: seq=176, flags=16, 128=335544321*
23:40:11 | [D] | [dgr:620] | Sending test3g message to network: seq=177, flags=16, 224=0,0,0,255,255,0
23:40:11 | [D] | [light:036] | 'internal_light' Setting:
23:40:11 | [D] | [light:070] | Cold white: 100%, warm white: 100%
23:40:11 | [D] | [light:085] | Transition length: 1.0s
23:40:11 | [D] | [dgr:620] | Received test3g message from local: seq=177, flags=16, 224=0,0,0,255,255,0*
23:40:11 | [D] | [dgr:620] | Sending test3g message to network: seq=178, flags=0, 5=255
23:40:11 | [I] | [light:392] | 'internal_light' - Keeping current color mode Cold/warm white for call without color mode.
23:40:11 | [D] | [light:036] | 'internal_light' Setting:
23:40:11 | [D] | [light:051] | Brightness: 100%
23:40:11 | [D] | [light:085] | Transition length: 1.0s
23:40:11 | [D] | [dgr:620] | Received test3g message from local: seq=178, flags=0, 5=255*
23:40:11 | [W] | [component:214] | Component api took a long time for an operation (0.13 s).
23:40:11 | [W] | [component:215] | Components should block for at most 20-30ms.
23:40:11 | [D] | [dgr:620] | Sending test3g message to network: seq=178, flags=0, 5=255
23:40:12 | [D] | [dgr:620] | Sending test3g message to 192.168.1.13: seq=178, flags=0, last ack=175
23:40:12 | [D] | [dgr:620] | Sending test3g message to 192.168.1.50: seq=178, flags=0, last ack=175
23:40:12 | [D] | [dgr:620] | Received test3g message from 192.168.1.50: seq=178, flags=8
23:40:12 | [D] | [dgr:620] | Received test3g message from 192.168.1.13: seq=178,

Treatlife bulb:

23:40:12 | [D] | [dgr:620] | Sending test3g message to 192.168.1.23: seq=178, flags=8
-- | -- | -- | --
23:40:12 | [I] | [light:392] | 'treatlife-store' - Keeping current color mode Cold/warm white for call without color mode.
23:40:12 | [D] | [light:036] | 'treatlife-store' Setting:
23:40:12 | [D] | [light:051] | Brightness: 100%
23:40:12 | [D] | [light:085] | Transition length: 0.5s
23:40:12 | [D] | [dgr:620] | Received test3g message from 192.168.1.23: seq=178, flags=0, 5=255*
23:41:06 | [D] | [dgr:620] | Sending test3g message to network: seq=42, flags=64

I tested again by adding a tasmota device in the same group, and I was able to toggle the light bulb just fine, even after it doesn't respond to the N wall switch.

Overview-.-Home-Assistant.webm

Treatlife bulb:

23:43:50 | [D] | [dgr:620] | Sending test3g message to 192.168.1.13: seq=25, flags=8
23:43:50 | [I] | [light:392] | 'treatlife-store' - Keeping current color mode Cold/warm white for call without color mode.
23:43:50 | [D] | [light:036] | 'treatlife-store' Setting:
23:43:50 | [D] | [light:047] | State: ON
23:43:50 | [D] | [light:085] | Transition length: 0.5s
23:43:50 | [D] | [dgr:620] | Received test3g message from 192.168.1.13: seq=25, flags=0, 128=16777217*
23:44:16 | [D] | [dgr:620] | Sending test3g message to network: seq=42, flags=64
23:45:15 | [D] | [dgr:620] | Sending test3g message to 192.168.1.13: seq=26, flags=8
23:45:15 | [D] | [light:036] | 'treatlife-store' Setting:
23:45:15 | [D] | [light:047] | State: OFF
23:45:15 | [D] | [light:085] | Transition length: 0.5s
23:45:15 | [D] | [dgr:620] | Received test3g message from 192.168.1.13: seq=26, flags=0, 128=16777216*
23:45:17 | [D] | [dgr:620] | Sending test3g message to network: seq=42, flags=64

Is there any other logs I could share that could provide more details on this issue?

Here's my esphome config for the switch & bulb for references:

substitutions:
  devicename: 3gang-switch-beken
  friendlyname: 3g-beken

esphome:
  name: $devicename
  friendly_name: $friendlyname 

bk72xx:
  board: generic-bk7231n-qfn32-tuya
  framework:
    version: latest

# Enable logging
logger: 
  
# Enable Home Assistant API
api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "3G-Beken Fallback Hotspot"
    password: !secret ap_password

captive_portal:
  
web_server:
  port: 80

globals:
  - id: counter #for switching to cold or warm light
    type: int
    restore_value: False
    initial_value: "0"

  - id: bool_dim_or_bright #false = dim, true = brighten
    type: bool
    restore_value: no
    initial_value: 'false'

sensor:
  - platform: uptime
    name: $friendlyname Uptime
    update_interval: 60s

  - platform: template
    id: local_brightness
    lambda: return id(internal_light).remote_values.get_brightness();
    update_interval: 60s

external_components:
  - source: github://cossid/tasmotadevicegroupsforesphome@main
    components: [ device_groups ]
    refresh: 10 min

device_groups:
  - group_name: "test3g"     # Tasmota device group name
    lights:
      - internal_light

output:
  - platform: template
    id: dummy_output
    type: float
    write_action:
      - lambda: return;

light:
  - platform: rgbww
    id: internal_light
    color_interlock: true
    cold_white_color_temperature: 6500 K
    warm_white_color_temperature: 2700 K
    red: dummy_output
    green: dummy_output
    blue: dummy_output
    cold_white: dummy_output
    warm_white: dummy_output

switch:
  - platform: gpio
    name: $devicename Switch 1
    pin: GPIO7
    id: relay_1

  - platform: gpio
    name: $devicename Switch 2
    pin: GPIO8
    id: relay_2
    restore_mode: ALWAYS_ON

  - platform: gpio
    name: $devicename Switch 3
    pin: GPIO9
    id: relay_3   


button:
  - platform: template
    name: "Internal light"
    on_press:
      - light.toggle:
          id: internal_light

binary_sensor:
  - platform: status
    name: $devicename Status

  - platform: gpio
    pin:
      number: GPIO14
      mode: INPUT_PULLUP
      inverted: True
    name: $devicename Button 1
    on_press:
      - switch.toggle: relay_1

  - platform: gpio
    pin:
      number: GPIO26
      mode: INPUT_PULLUP
      inverted: True
    name: $devicename Button 2
    id: button2
    on_multi_click:
        # single click
      - timing:
          - ON for at most 1s
          - OFF for at least 0.5s
        then:         
          - light.toggle: internal_light
# double click to change          
      - timing:
          - ON for at most 1s
          - OFF for at most 1s
          - ON for at most 1s
          - OFF for at least 0.2s
        then:
          - lambda: |- 
              auto call = id(internal_light).turn_on();

              if (id(counter) == 0) {
                call.set_cold_white(1.0);  //cold white
                call.set_brightness(1.0);
              }

              if (id(counter) == 1) {
                call.set_cold_white(0.0); 
                call.set_warm_white(1.0);  //warm white
                call.set_brightness(0.5);
              }

              if (id(counter) < 1) { 
                id(counter) += 1;
              } else {
                id(counter) = 0;
              }

              call.perform();

#for dimming lights
    on_press:
      then:
      - if:
          condition: 
              lambda: |-
                return id(bool_dim_or_bright);
# When above condition evaluates to true - brighter function else dimmer
          then:
          - delay: 0.5s
          - while:
              condition:
                binary_sensor.is_on: button2
# This is to set the minimum value so that touch sensor only allows pre-set minimum
                  # - sensor.in_range:
                  #     id: local_brightness
                  #     above: 10
              then:
                - light.dim_relative:
                    id: internal_light
                    relative_brightness: 5%
                    transition_length: 0.1s
                - delay: 0.1s
          - lambda: |-
              id(bool_dim_or_bright) = (false);
          else:
          - delay: 0.5s
          - while:
              condition:
                and:
                  - binary_sensor.is_on: button2
              then:
                - light.dim_relative:
                    id: internal_light
                    relative_brightness: -5%
                    transition_length: 0.1s
                - delay: 0.1s
          - lambda: |-
              id(bool_dim_or_bright) = (true);


  - platform: gpio
    pin:
      number: GPIO24
      mode: INPUT_PULLUP
      inverted: True
    name: $devicename Button 3
    on_press:
      - switch.toggle: relay_3

status_led:
  pin:
    number: GPIO6
    inverted: False

Treatlife bulb:

substitutions:
  devicename: treatlife-store

esphome:
  name: $devicename
  friendly_name: treatlife-store

bk72xx:
  board: generic-bk7231t-qfn32-tuya
  framework:
    version: latest

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
    priority: 1
  - ssid: !secret wifi2_ssid
    password: !secret wifi2_password
    priority: 2
  output_power: 10dB

  # Enable fallback hotspot in case wifi connection fails
  ap:
    password: !secret ap_password

web_server:
  port: 80

captive_portal:

external_components:
  - source: github://cossid/tasmotadevicegroupsforesphome@main
    components: [ device_groups ]
    refresh: 10 min

device_groups:
  - group_name: "test3g"     # Tasmota device group name
    lights:
      - light_treatlife_store

light:
  - platform: rgbww
    name: $devicename
    id: "light_treatlife_store"
    red: output_red
    green: output_green
    blue: output_blue
    warm_white: output_warmwhite
    cold_white: output_coldwhite
    color_interlock: true
    cold_white_color_temperature: 6500 K
    warm_white_color_temperature: 2700 K
    default_transition_length: 0.5s
    restore_mode: RESTORE_DEFAULT_ON

sm2135:
  data_pin: GPIO8
  clock_pin: GPIO7
  rgb_current: 25mA
  cw_current: 40mA

output:
  - platform: sm2135
    id: output_red
    channel: 2
    max_power: 0.9
  - platform: sm2135
    id: output_green
    channel: 0
    max_power: 0.9
  - platform: sm2135
    id: output_blue
    channel: 1
    max_power: 0.9
  - platform: sm2135
    id: output_warmwhite
    channel: 4
    max_power: 0.9
  - platform: sm2135
    id: output_coldwhite
    channel: 3
    max_power: 0.9

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.