Code Monkey home page Code Monkey logo

pyplumio's Introduction

PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.

PyPI version PyPI Supported Python Versions PyPlumIO CI Maintainability Test Coverage stability-release-candidate Ruff

Overview

This package aims to provide complete and easy to use solution for communicating with climate devices by Plum Sp. z o.o.

ecoMAX controllers

Currently it supports reading and writing parameters of ecoMAX controllers by Plum Sp. z o.o., getting service password and sending network information to show on controller's display.

Devices can be connected directly via RS-485 to USB adapter or through network by using RS-485 to Ethernet/WiFi converter.

RS-485 converters

Table of contents

Quickstart

  1. To use PyPlumIO, first install it using pip:
$ pip install pyplumio
  1. Connect to the ecoMAX controller:
>>> connection = pyplumio.open_serial_connection("/dev/ttyUSB0")
>>> await connection.connect()
>>> ecomax = await connection.get("ecomax")
  1. Print some values:
>>> print(await ecomax.get("heating_temp"))
  1. Don’t forget to close the connection:
>>> await connection.close()

Home Assistant Integration

There is companion Home Assistant integration that is being co-developed with this package and depends on it. Click button below to check it out.

Plum ecoMAX for Home Assistant

Attribution

Special thanks to econetanalyze project by twkrol for initial information about protocol.

License

This product is distributed under MIT license.

pyplumio's People

Contributors

denpamusic avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

szczepanleon

pyplumio's Issues

Incorrect fuel level.

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

Integration shows incorrect fuel level.
When the boiler is 100% in the lovalace I have 201%.

obraz

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

No response

My diagnostics data:

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Cannot connect to ecoMAX850r2

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

Hi,

I'm having issues with adding device in HA. I've connected EW11a RS-485-WIFI module to ecoSTER pins on the board.

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/pyplumio/protocol.py", line 229, in frame_consumer
device.handle_frame(frame)
File "/usr/local/lib/python3.11/site-packages/pyplumio/devices/ecomax.py", line 139, in handle_frame
super().handle_frame(frame)
File "/usr/local/lib/python3.11/site-packages/pyplumio/devices/__init__.py", line 132, in handle_frame
if frame.data is not None:
^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/frames/__init__.py", line 159, in data
self.decode_message(self._message) if self._message is not None else {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/frames/responses.py", line 222, in decode_message
return SchedulesStructure(self).decode(message)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/structures/schedules.py", line 188, in decode
start = message[offset + 1]
~~~~~~~^^^^^^^^^^^^
IndexError: index out of range
2024-01-17 15:09:30.316 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/pyplumio/protocol.py", line 229, in frame_consumer
device.handle_frame(frame)
File "/usr/local/lib/python3.11/site-packages/pyplumio/devices/ecomax.py", line 139, in handle_frame
super().handle_frame(frame)
File "/usr/local/lib/python3.11/site-packages/pyplumio/devices/__init__.py", line 132, in handle_frame
if frame.data is not None:
^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/frames/__init__.py", line 159, in data
self.decode_message(self._message) if self._message is not None else {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/frames/responses.py", line 222, in decode_message
return SchedulesStructure(self).decode(message)[0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/structures/schedules.py", line 188, in decode
start = message[offset + 1]
~~~~~~~^^^^^^^^^^^^
IndexError: index out of range
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/pyplumio/stream.py", line 46, in close
await self.wait_closed()
File "/usr/local/lib/python3.11/site-packages/pyplumio/helpers/timeout.py", line 21, in wrapper
return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/tasks.py", line 489, in wait_for
return fut.result()
^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/pyplumio/stream.py", line 53, in wait_closed
await self._writer.wait_closed()
File "/usr/local/lib/python3.11/asyncio/streams.py", line 349, in wait_closed
await self._protocol._get_close_waiter(self)
File "/usr/local/lib/python3.11/asyncio/selector_events.py", line 1051, in write
n = self._sock.send(data)
^^^^^^^^^^^^^^^^^^^^^
BrokenPipeError: [Errno 32] Broken pipe

My diagnostics data:

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Ecomax 850I support

Is there an existing issue for this?

  • I have searched the existing issues

I want to suggest:

  • support for a new product
  • new functionality for a product
  • documentation improvement
  • other

Feature description

I have Ecomax 850i3 standalone controller. It will be fine to have this controller supported. Can assist of getting required information for implementation.

Additional information

No response

Product page

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Unable to change thermostat parameters

Hi,

hysteresis on start: 0.6

thermo = await ecomax.get_value("thermostats");
print(await thermo[0].get_value('hysteresis'))
await thermo[0].set_value("hysteresis", 8)
print(await thermo[0].get_value('hysteresis'))

6
Timed out while trying to set 'hysteresis' parameter
8

every parameter on thermostats, when i try change i got "Timed out"

i try change day_target_temp 22.1 >> 21.5
i got Timeout, but day_target_temp changed to 47 (max 35)
i try one more time and still got Timeout but temp was 21.5

Originally posted by @entmor in #8 (comment)

can't import pyplumio when i launch my script

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

Hi,
First thank you so much for your so interresting job, it make me want to learn Python. i'm a noob on python so i hope i will not disturb you to much.

Sorry fo my english i'm french :§)

i'm experiencing the same problem on mac, Wndows and Raspi, i try to launch this script

import asyncio
import pyplumio

async def main():
async with pyplumio.open_tcp_connection("192.168.20.50", 8899) as connection:
ecomax = await connection.get_device("ecomax")
print(await ecomax.get_value("heating_temp"))

asyncio.run(main())

and i have this message

python3 test.py
Traceback (most recent call last):
File "/home/pi/test.py", line 2, in
import pyplumio
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/init.py", line 4, in
from pyplumio.connection import Connection, SerialConnection, TcpConnection
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/connection.py", line 15, in
from pyplumio.protocol import Protocol
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/protocol.py", line 16, in
from pyplumio.frames.requests import StartMasterRequest
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/frames/requests.py", line 15, in
from pyplumio.frames.responses import DeviceAvailableResponse, ProgramVersionResponse
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/frames/responses.py", line 13, in
from pyplumio.structures.network_info import NetworkInfoStructure
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/structures/network_info.py", line 7, in
from pyplumio.helpers.network_info import (
File "/home/pi/.local/lib/python3.11/site-packages/pyplumio/helpers/network_info.py", line 43, in
@DataClass
^^^^^^^^^
File "/usr/local/lib/python3.11/dataclasses.py", line 1220, in dataclass
return wrap(cls)
^^^^^^^^^
File "/usr/local/lib/python3.11/dataclasses.py", line 1210, in wrap
return _process_class(cls, init, repr, eq, order, unsafe_hash,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dataclasses.py", line 958, in _process_class
cls_fields.append(_get_field(cls, name, type, kw_only))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dataclasses.py", line 815, in _get_field
raise ValueError(f'mutable default {type(f.default)} for field '
ValueError: mutable default <class 'pyplumio.helpers.network_info.EthernetParameters'> for field eth is not allowed: use default_factory

For information i use the home assistant plungin and it works

My environment is:

- OS: Windows 10, Raspi buster
- Python: Python 3.11

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Parameters should be compared with values and boundaries

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

Both parameter's value and boundaries should be used when comparing parameters to each other. Currently only parameter's value is compared, which leads to a problems when using on_change filter.

My environment is:

- OS: Ubuntu
- Python: 3.x

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

PyPlumIO doesn't reconnect forever on TCP connection failure.

The TCP device relaying my RS485 connection disappears for some reason, the custom component retries connection to it for 5 times and then goes forever silent. If I do not switch on the TCP device back on in time (100s.) the plugin remains dormant forever until I reload it from HASS (or restart HASS). I don't know if this is desired behavior, but it annoyed me a bit and I increased the retry count to 50 for start to be able to play around with devices and locations etc.

last version

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

Hi,

i would like try last version pyplumIO, but i got error:

Zrzut ekranu 2022-12-30 o 01 07 16

and i test only simple code:

Zrzut ekranu 2022-12-30 o 01 08 37

so, i got device status (but i dont check) and error

My environment is:

- OS: Ubuntu 22.04
- Python: 3.10

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

USB to RS-485 adapter

I'm seeing following log messages:

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

arduino

Is there an existing issue for this?

  • I have searched the existing issues

I want to suggest:

  • support for a new product
  • new functionality for a product
  • documentation improvement
  • other

Feature description

Could you please make a version of this library for arduino/esp? Or at least make a tutorial for rasberry? I tried your serial line method, but unfortunately the console output was nothing (the temperature should have been displayed). Thank you

Additional information

No response

Product page

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Callback needs to run 2-3 times before it changes the parameters value (e.g. devices.ecomax.boiler_control).

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

When I try to use the following example (in README.md) to write a value to a parameter (e.g. boiler_control), it doesn't write the value on the first try, I need to wait for the callback to run 2-3 times before it changes the actual value.

from pyplumio import TcpConnection
from pyplumio.devices import DeviceCollection

async def my_callback(devices: DeviceCollection, connection):
    if devices.has("ecomax") and devices.ecomax.has("boiler_control"):
    	devices.ecomax.boiler_control = 1
        connection.close()

connection = TcpConnection(host=myipaddress, port=myport)
connection.run(main, interval=3)

So I need to wait 6-9 seconds (2-3x interval) for the code to write the value, and that means I can't use connection.close(), since it closes the connection after the first time of the callback being run.

When the script contains connection.close(), I get no output and nothing gets changed.

image

But when removing connection.close() from the script, I waited 4 seconds and killed the script, but the value was changed.

image

When I did print(devices.ecomax.data) the product model printed was ecoMAX920P1-T instead of EcoMax 860P.

The home assistant plugin works perfectly, unlike the python script.

My environment is:

- OS: Debian 11.3 (Latest Raspberry Pi x64 image)
- OS: Windows 10
- Python: 3.10
- Device: EcoMax 860P
- PyPlumIO Version: 0.1.17 (Latest)

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

Frame error: Incorrect frame length. Expected 367 bytes, got 356 bytes

Code of Conduct

  • I agree to follow this project's Code of Conduct

ecoNET300

Is there an existing issue for this?

  • I have searched the existing issues

I want to suggest:

  • support for a new product
  • new functionality for a product
  • documentation improvement
  • other

Feature description

I got with boiler with Regulator ecoMAX800P. And I remotely connect by econect ecoNET300 remotely by web and locally by IP address. Does this library support this ecoNET300 device by TCP (I mean locally)?

Additional information

No response

Product page

https://www.plum.pl/en/project/econet300_en/

Code of Conduct

  • I agree to follow this project's Code of Conduct

IndexError when using number of currently connected thermostats to enumerate thermostat parameters

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

When enumerating thermostat parameters, total number of thermostats supported by the controller should be used, instead of number of currently connected thermostats.

Initially described by @jszkiela72 in #19 (comment)

(I went back to the stable version because I didn't have a thermostat in it.)

My environment is:

- OS: Windows 11 WSL 2 (Ubuntu 22.04.3 LTS)
- Python: 3.11.5

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

self = <[IndexError('tuple index out of range') raised in repr()] ThermostatParametersStructure object at 0x7fd9426b4d00>
message = bytearray(b'\x00\x00%\x00\x00\x05\x00\x00\x07\xdc\x00d\x00^\x01\x96\x00d\x00^\x01d<\x8c\x02\x00<\x01\x00<\x01\x00<\n\x...f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff')
thermostats = 1, start = 0, end = 37

    def _thermostat_parameter(
        self, message: bytearray, thermostats: int, start: int, end: int
    ) -> Generator[tuple[int, ParameterValues], None, None]:
        """Get a single thermostat parameter."""
        for index in range(start, (start + end) // thermostats):
>           description = THERMOSTAT_PARAMETERS[index]
E           IndexError: tuple index out of range

pyplumio/structures/thermostat_parameters.py:194: IndexError

Code of Conduct

  • I agree to follow this project's Code of Conduct

Can't process received frame

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

not a real issue, but looks like some frames have another checksum algo or something. I think its so, because of pattern in bad checksums... see logs.

My environment is:

- OS: Ubuntu 22.04
- Python: 3.11

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

USB to RS-485 adapter

I'm seeing following log messages:

Sep 25 14:10:26  hass[1654340]: 2023-09-25 14:10:26.024 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (0)
Sep 25 14:13:09  hass[1654340]: 2023-09-25 14:13:09.099 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (22)
Sep 25 14:16:59  hass[1654340]: 2023-09-25 14:16:59.590 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (0)
Sep 25 14:22:49  hass[1654340]: 2023-09-25 14:22:49.956 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (10)
Sep 25 14:32:56  hass[1654340]: 2023-09-25 14:32:56.410 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (22)
Sep 25 14:41:53  hass[1654340]: 2023-09-25 14:41:53.140 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (104)
Sep 25 15:00:16  hass[1654340]: 2023-09-25 15:00:16.113 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (22)
Sep 25 15:01:37  hass[1654340]: 2023-09-25 15:01:37.875 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (104)
Sep 25 15:03:51  hass[1654340]: 2023-09-25 15:03:51.178 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (81)
Sep 25 15:03:59  hass[1654340]: 2023-09-25 15:03:59.030 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (22)
Sep 25 15:10:01  hass[1654340]: 2023-09-25 15:10:01.552 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (252)
Sep 25 15:11:56  hass[1654340]: 2023-09-25 15:11:56.533 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (104)
Sep 25 15:21:09  hass[1654340]: 2023-09-25 15:21:09.729 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (10)
Sep 25 15:21:45  hass[1654340]: 2023-09-25 15:21:45.386 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (69)
Sep 25 15:34:35  hass[1654340]: 2023-09-25 15:34:35.075 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (35)
Sep 25 15:40:16  hass[1654340]: 2023-09-25 15:40:16.367 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (0)
Sep 25 15:43:42  hass[1654340]: 2023-09-25 15:43:42.466 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (81)
Sep 25 15:45:55  hass[1654340]: 2023-09-25 15:45:55.034 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (10)
Sep 25 15:47:33  hass[1654340]: 2023-09-25 15:47:33.650 WARNING (MainThread) [pyplumio.protocol] Can't process received frame: Incorrect frame checksum (0)


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

Unable to get/set schedule.

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

  1. When I run the "Plum ecoMAX: get_schedule" service for heating, the integration returns all values as "day". This is not correct because in the controller I have it set from 00.00 to 06.00 "night" from 6.00 to 15.00 "day" from 15.00 to 00.00 "night" "
    I can't save the schedule to the controller using the Plum ecoMAX service: set_schedule" for heating.

  2. When I run the "Plum ecoMAX: get_schedule" service for water heater, the integration returns the correct values.
    But I can't save the schedule to the controller using the Plum ecoMAX service: set_schedule" for the water heater.

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

No response

My diagnostics data:

config_entry-plum_ecomax-3d173fc70d439aca63ce86c959ccc479.json.txt

Code of Conduct

  • I agree to follow this project's Code of Conduct

Regdata becomes permanently unavailable if schema is corrupted during startup

Hi,

Sometimes on start app i got message like:

Can't process received frame: Incorrect frame checksum (1)
Can't process received frame: Incorrect frame checksum (22)

and when i try get regdata (or subscribe regdata),
i got nothing. ( connection can be open next 10, 20 min or more and still regdata (subscribe) will be not available)

I must restart app (reconnect) and try again get regdata (with timeout=15), when i got regdata message, then i know, that will be ok.

Originally posted by @entmor in #8 (comment)

Total burned fuel

Is there an existing issue for this?

  • I have searched the existing issues

I want to suggest:

  • support for a new product
  • new functionality for a product
  • documentation improvement
  • other

Feature description

Hi,

How can we get value something like "total_fuel_burned_kg"? i know only, that value is in regularData from ecomax[frame=8] and when i check regdata from ecomax, it is key 185
Zrzut ekranu 2022-12-19 o 23 50 39

btw. thanks you for your job. really nice lib

Additional information

No response

Product page

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

ValueError: filedescriptor out of range in select()

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

probably its pyserial-asyncio bug, but...
some random time after hass running it becomes unresponsive and I see in logs SEE BELOW
Something in my setup acquiring file descriptors and not closing them... I guess its you...

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

USB to RS-485 adapter

I'm seeing following log messages:

Sep 25 00:46:35  hass[356102]:   File "/usr/lib/python3.11/asyncio/events.py", line 80, in _run
Sep 25 00:46:35  hass[356102]:     self._context.run(self._callback, *self._args)
Sep 25 00:46:35  hass[356102]:   File "/home/swex/.homeassistant/deps/lib/python3.11/site-packages/serial_asyncio/__init__.py", line 115, in _read_ready
Sep 25 00:46:35  hass[356102]:     data = self._serial.read(self._max_read_size)
Sep 25 00:46:35  hass[356102]:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sep 25 00:46:35  hass[356102]:   File "/usr/lib/python3/dist-packages/serial/serialposix.py", line 565, in read
Sep 25 00:46:35  hass[356102]:     ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left())
Sep 25 00:46:35  hass[356102]:                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sep 25 00:46:35  hass[356102]: ValueError: filedescriptor out of range in select()


### My diagnostics data:

{
  "home_assistant": {
    "installation_type": "Unknown",
    "version": "2023.9.2",
    "dev": false,
    "hassio": false,
    "virtualenv": false,
    "python_version": "3.11.0rc1",
    "docker": false,
    "arch": "x86_64",
    "os_name": "Linux",
    "os_version": "5.15.0-84-generic",
    "run_as_root": false
  },
  "custom_components": {
    "plum_ecomax": {
      "version": "0.3.8",
      "requirements": [
        "pyplumio==0.4.2.post1"
      ]
    },
    "tcl_tv_remote": {
      "version": "0.1.3",
      "requirements": [
        "TCL-TV-Remote==0.0.1"
      ]
    },
    "hacs": {
      "version": "1.32.1",
      "requirements": [
        "aiogithubapi>=22.10.1"
      ]
    },
    "yi_hack": {
      "version": "0.4.6",
      "requirements": []
    }
  },
  "integration_manifest": {
    "domain": "plum_ecomax",
    "name": "Plum ecoMAX",
    "codeowners": [
      "@denpamusic"
    ],
    "config_flow": true,
    "dependencies": [
      "network"
    ],
    "documentation": "https://github.com/denpamusic/homeassistant-plum-ecomax",
    "integration_type": "hub",
    "iot_class": "local_push",
    "issue_tracker": "https://github.com/denpamusic/homeassistant-plum-ecomax/issues",
    "loggers": [
      "pyplumio"
    ],
    "requirements": [
      "pyplumio==0.4.2.post1"
    ],
    "version": "0.3.8",
    "is_built_in": false
  },
  "data": {
    "entry": {
      "title": "ecoMAX 350P2-C",
      "data": {
        "device": "/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0",
        "baudrate": "115200",
        "connection_type": "Serial",
        "uid": "**REDACTED**",
        "model": "ecoMAX 350P2-C",
        "product_type": 0,
        "product_id": 138,
        "software": "3.12.20.K1",
        "sub_devices": [
          "thermostats"
        ]
      }
    },
    "pyplumio": {
      "version": "0.4.2.post1"
    },
    "data": {
      "connected": false,
      "frame_versions": {
        "49": 3,
        "50": 3,
        "54": 1,
        "56": 2,
        "57": 1,
        "61": 9070,
        "80": 1,
        "81": 1,
        "82": 1,
        "83": 1
      },
      "regdata_decoder": true,
      "ecomax_control": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxBinaryParameter'>",
        "repr": "EcomaxBinaryParameter(device=EcoMAX, description=EcomaxParameterDescription(name='ecomax_control', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxBinaryParameter'>, multiplier=1, offset=0), value=on, min_value=off, max_value=on)"
      },
      "state": 3,
      "fan": true,
      "feeder": false,
      "heating_pump": true,
      "water_heater_pump": false,
      "circulation_pump": false,
      "lighter": false,
      "alarm": false,
      "outer_boiler": false,
      "fan2_exhaust": false,
      "feeder2": false,
      "outer_feeder": false,
      "solar_pump": false,
      "fireplace_pump": false,
      "gcz_contact": false,
      "blow_fan1": false,
      "blow_fan2": false,
      "heating_pump_flag": true,
      "water_heater_pump_flag": true,
      "circulation_pump_flag": true,
      "solar_pump_flag": false,
      "heating_temp": 60.751739501953125,
      "feeder_temp": 26.1065673828125,
      "optical_temp": 100.0,
      "heating_target": 70,
      "heating_status": 0,
      "water_heater_target": 45,
      "water_heater_status": 128,
      "pending_alerts": 0,
      "fuel_level": 100,
      "transmission": 0,
      "fan_power": 32.04085159301758,
      "load": 58,
      "power": 27.90319061279297,
      "fuel_burned": 0.0014456380461813543,
      "fuel_consumption": 5.356383323669434,
      "thermostat": 1,
      "modules": {
        "__type": "<class 'pyplumio.structures.modules.ConnectedModules'>",
        "repr": "ConnectedModules(module_a='3.12.20.K1', module_b=None, module_c=None, ecolambda=None, ecoster=None, panel=None)"
      },
      "thermostats": {},
      "thermostat_sensors": true,
      "thermostat_count": 3,
      "sensors": true,
      "schema": [
        [
          1792,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'\\x02', size=1)"
          }
        ],
        [
          1536,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          1538,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          1542,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          1541,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          5,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          3,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          1543,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=0)"
          }
        ],
        [
          1544,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\t', size=1)"
          }
        ],
        [
          1545,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          1546,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          1547,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          1548,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          1549,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          2,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          6,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=0)"
          }
        ],
        [
          6,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Boolean'>",
            "repr": "Boolean(data=b'\\x00', size=1)"
          }
        ],
        [
          1024,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\xc8\\x01sB', size=4)"
          }
        ],
        [
          1027,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'h\\xdcv\\xc3', size=4)"
          }
        ],
        [
          1026,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'@\\xda\\xd0A', size=4)"
          }
        ],
        [
          26,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\xc8B', size=4)"
          }
        ],
        [
          1025,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b' \\xbeC\\xc3', size=4)"
          }
        ],
        [
          29,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b' \\xbeC\\xc3', size=4)"
          }
        ],
        [
          1028,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          1032,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          1031,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          1029,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          25,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          27,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          29,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          29,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          29,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          29,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Float'>",
            "repr": "Float(data=b'\\x00\\x00\\x00\\x00', size=4)"
          }
        ],
        [
          1280,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'F', size=1)"
          }
        ],
        [
          1283,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'\\x00', size=1)"
          }
        ],
        [
          1282,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'\\x00', size=1)"
          }
        ],
        [
          1287,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'(', size=1)"
          }
        ],
        [
          1281,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'-', size=1)"
          }
        ],
        [
          1288,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'(', size=1)"
          }
        ],
        [
          2048,
          {
            "__type": "<class 'pyplumio.helpers.data_types.Byte'>",
            "repr": "Byte(data=b'\\x00', size=1)"
          }
        ]
      ],
      "alerts": [
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 9, 24, 16, 38), to_dt=datetime.datetime(2023, 9, 25, 0, 26, 28))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 9, 22, 9, 6), to_dt=datetime.datetime(2023, 9, 22, 11, 15, 7))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 9, 19, 14, 43), to_dt=datetime.datetime(2023, 9, 19, 14, 51, 37))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 9, 10, 15, 58), to_dt=datetime.datetime(2023, 9, 10, 18, 0, 43))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 9, 9, 14, 43), to_dt=datetime.datetime(2023, 9, 9, 14, 47, 50))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.KINDLING_FAILURE: 7>, from_dt=datetime.datetime(2023, 9, 9, 14, 33, 38), to_dt=datetime.datetime(2023, 9, 9, 14, 33, 50))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.KINDLING_FAILURE: 7>, from_dt=datetime.datetime(2023, 9, 9, 14, 11, 54), to_dt=datetime.datetime(2023, 9, 9, 14, 12, 26))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 8, 31, 15, 6), to_dt=datetime.datetime(2023, 8, 31, 15, 19, 33))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 8, 24, 1, 10), to_dt=datetime.datetime(2023, 8, 29, 19, 49, 10))"
        },
        {
          "__type": "<class 'pyplumio.structures.alerts.Alert'>",
          "repr": "Alert(code=<AlertType.POWER_LOSS: 0>, from_dt=datetime.datetime(2023, 8, 24, 0, 57), to_dt=datetime.datetime(2023, 8, 24, 1, 0, 29))"
        }
      ],
      "schedules": {
        "heating": {
          "__type": "<class 'pyplumio.helpers.schedule.Schedule'>",
          "repr": "Schedule(name='heating', device=<pyplumio.devices.ecomax.EcoMAX object at 0x7fdc680efd50>, monday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), tuesday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), wednesday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), thursday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), friday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), saturday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), sunday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]))"
        },
        "boiler_work": {
          "__type": "<class 'pyplumio.helpers.schedule.Schedule'>",
          "repr": "Schedule(name='boiler_work', device=<pyplumio.devices.ecomax.EcoMAX object at 0x7fdc680efd50>, monday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), tuesday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), wednesday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), thursday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), friday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), saturday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]), sunday=ScheduleDay([True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True]))"
        }
      },
      "heating_schedule_switch": {
        "__type": "<class 'pyplumio.structures.schedules.ScheduleBinaryParameter'>",
        "repr": "ScheduleBinaryParameter(device=EcoMAX, description=ScheduleParameterDescription(name='heating_schedule_switch', cls=<class 'pyplumio.structures.schedules.ScheduleBinaryParameter'>), value=off, min_value=off, max_value=on)"
      },
      "heating_schedule_parameter": {
        "__type": "<class 'pyplumio.structures.schedules.ScheduleParameter'>",
        "repr": "ScheduleParameter(device=EcoMAX, description=ScheduleParameterDescription(name='heating_schedule_parameter', cls=<class 'pyplumio.structures.schedules.ScheduleParameter'>), value=0, min_value=0, max_value=30)"
      },
      "boiler_work_schedule_switch": {
        "__type": "<class 'pyplumio.structures.schedules.ScheduleBinaryParameter'>",
        "repr": "ScheduleBinaryParameter(device=EcoMAX, description=ScheduleParameterDescription(name='boiler_work_schedule_switch', cls=<class 'pyplumio.structures.schedules.ScheduleBinaryParameter'>), value=off, min_value=off, max_value=on)"
      },
      "boiler_work_schedule_parameter": {
        "__type": "<class 'pyplumio.structures.schedules.ScheduleParameter'>",
        "repr": "ScheduleParameter(device=EcoMAX, description=ScheduleParameterDescription(name='boiler_work_schedule_parameter', cls=<class 'pyplumio.structures.schedules.ScheduleParameter'>), value=0, min_value=0, max_value=30)"
      },
      "schedule_parameters": true,
      "mixer_parameters": false,
      "product": {
        "__type": "<class 'pyplumio.structures.product_info.ProductInfo'>",
        "repr": "ProductInfo(type=<ProductType.ECOMAX_P: 0>, id=138, uid='**REDACTED**', logo=35328, image=2816, model='EM350P2-C')"
      },
      "airflow_power_100": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_100', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=36.0, min_value=31.0, max_value=70.0)"
      },
      "airflow_power_50": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_50', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=30.0, min_value=26.0, max_value=35.0)"
      },
      "airflow_power_30": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_30', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=25.0, min_value=19.0, max_value=29.0)"
      },
      "cycle_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='cycle_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=15.0, min_value=1.0, max_value=250.0)"
      },
      "h2_hysteresis": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='h2_hysteresis', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=3.0, min_value=1.0, max_value=30.0)"
      },
      "h1_hysteresis": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='h1_hysteresis', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=1.0, min_value=1.0, max_value=30.0)"
      },
      "heating_hysteresis": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='heating_hysteresis', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=5.0, min_value=1.0, max_value=30.0)"
      },
      "fuzzy_logic": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxBinaryParameter'>",
        "repr": "EcomaxBinaryParameter(device=EcoMAX, description=EcomaxParameterDescription(name='fuzzy_logic', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxBinaryParameter'>, multiplier=1, offset=0), value=on, min_value=off, max_value=on)"
      },
      "min_fuzzy_logic_power": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='min_fuzzy_logic_power', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=0.0, min_value=0.0, max_value=100.0)"
      },
      "max_fuzzy_logic_power": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='max_fuzzy_logic_power', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=100.0, min_value=0.0, max_value=100.0)"
      },
      "min_fan_power": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='min_fan_power', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=19.0, min_value=10.0, max_value=100.0)"
      },
      "airflow_power_grate": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_grate', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=30.0, min_value=19.0, max_value=100.0)"
      },
      "boiler_hysteresis_grate": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='boiler_hysteresis_grate', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=4.0, min_value=1.0, max_value=30.0)"
      },
      "supervision_work_airflow": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='supervision_work_airflow', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=20.0, min_value=0.0, max_value=100.0)"
      },
      "supervision_work_airflow_brake": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='supervision_work_airflow_brake', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=8.0, min_value=1.0, max_value=250.0)"
      },
      "heating_temp_grate": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='heating_temp_grate', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=40.0, min_value=40.0, max_value=85.0)"
      },
      "fuel_detection_time_grate": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='fuel_detection_time_grate', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=15.0, min_value=0.0, max_value=250.0)"
      },
      "airflow_power_kindle": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_kindle', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=28.0, min_value=19.0, max_value=45.0)"
      },
      "scavenge_kindle": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='scavenge_kindle', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=10.0, min_value=10.0, max_value=240.0)"
      },
      "kindle_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='kindle_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=6.0, min_value=1.0, max_value=20.0)"
      },
      "warming_up_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='warming_up_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=20.0, min_value=1.0, max_value=250.0)"
      },
      "kindle_finish_threshold": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='kindle_finish_threshold', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=5.0, min_value=1.0, max_value=100.0)"
      },
      "min_kindle_power_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='min_kindle_power_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=2.0, min_value=0.0, max_value=100.0)"
      },
      "scavenge_after_kindle": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='scavenge_after_kindle', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=50.0, min_value=1.0, max_value=200.0)"
      },
      "airflow_power_after_kindle": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_after_kindle', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=26.0, min_value=19.0, max_value=35.0)"
      },
      "supervision_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='supervision_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=5.0, min_value=0.0, max_value=60.0)"
      },
      "supervision_cycle_duration": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='supervision_cycle_duration', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=40.0, min_value=1.0, max_value=250.0)"
      },
      "airflow_power_supervision": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_supervision', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=22.0, min_value=19.0, max_value=100.0)"
      },
      "max_extinguish_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='max_extinguish_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=12.0, min_value=1.0, max_value=60.0)"
      },
      "min_extinguish_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='min_extinguish_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=4.0, min_value=1.0, max_value=60.0)"
      },
      "airflow_power_extinguish": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_extinguish', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=80.0, min_value=19.0, max_value=100.0)"
      },
      "airflow_work_extinguish": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_work_extinguish', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=40.0, min_value=1.0, max_value=100.0)"
      },
      "airflow_brake_extinguish": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_brake_extinguish', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=15.0, min_value=10.0, max_value=250.0)"
      },
      "scavenge_start_extinguish": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='scavenge_start_extinguish', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=10.0, min_value=1.0, max_value=100.0)"
      },
      "scavenge_stop_extinguish": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='scavenge_stop_extinguish', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=5.0, min_value=1.0, max_value=100.0)"
      },
      "clean_begin_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='clean_begin_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=10.0, min_value=10.0, max_value=250.0)"
      },
      "extinguish_clean_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='extinguish_clean_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=2.0, min_value=1.0, max_value=30.0)"
      },
      "airflow_power_clean": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='airflow_power_clean', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=100.0, min_value=19.0, max_value=100.0)"
      },
      "warming_up_brake_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='warming_up_brake_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=24.0, min_value=1.0, max_value=250.0)"
      },
      "fuel_flow_kg_h": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='fuel_flow_kg_h', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=10, offset=0), value=5.3, min_value=0.1, max_value=25.0)"
      },
      "fuel_factor": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='fuel_factor', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=2.0, min_value=1.0, max_value=100.0)"
      },
      "fuel_calorific_value_kwh_kg": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='fuel_calorific_value_kwh_kg', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=10, offset=0), value=5.2, min_value=0.1, max_value=25.0)"
      },
      "fuel_detection_time": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='fuel_detection_time', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=1.0, min_value=0.0, max_value=5.0)"
      },
      "heating_target_temp": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='heating_target_temp', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=70.0, min_value=36.0, max_value=85.0)"
      },
      "min_heating_target_temp": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='min_heating_target_temp', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=36.0, min_value=30.0, max_value=80.0)"
      },
      "max_heating_target_temp": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='max_heating_target_temp', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=85.0, min_value=30.0, max_value=90.0)"
      },
      "heating_pump_on_temp": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='heating_pump_on_temp', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=40.0, min_value=30.0, max_value=80.0)"
      },
      "pause_heating_for_water_heater": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='pause_heating_for_water_heater', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=0.0, min_value=0.0, max_value=99.0)"
      },
      "increase_heating_temp_for_water_heater": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='increase_heating_temp_for_water_heater', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=5.0, min_value=3.0, max_value=15.0)"
      },
      "term_boiler_mode": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxBinaryParameter'>",
        "repr": "EcomaxBinaryParameter(device=EcoMAX, description=EcomaxParameterDescription(name='term_boiler_mode', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxBinaryParameter'>, multiplier=1, offset=0), value=off, min_value=off, max_value=on)"
      },
      "boiler_alert_temp": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='boiler_alert_temp', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=90.0, min_value=85.0, max_value=95.0)"
      },
      "max_feeder_temp": {
        "__type": "<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>",
        "repr": "EcomaxParameter(device=EcoMAX, description=EcomaxParameterDescription(name='max_feeder_temp', cls=<class 'pyplumio.structures.ecomax_parameters.EcomaxParameter'>, multiplier=1, offset=0), value=60.0, min_value=40.0, max_value=90.0)"
      },
      "ecomax_parameters": true,
      "thermostat_profile": null,
      "thermostat_parameters": false,
      "thermostat_parameters_decoder": true,
      "password": "**REDACTED**",
      "frame_errors": [],
      "loaded": true,
      "regdata": {
        "1792": 2,
        "1536": true,
        "1538": false,
        "1542": false,
        "1541": true,
        "5": false,
        "3": false,
        "1543": false,
        "1544": false,
        "1545": false,
        "1546": false,
        "1547": false,
        "1548": false,
        "1549": false,
        "2": false,
        "6": false,
        "1024": 60.751739501953125,
        "1027": -246.8609619140625,
        "1026": 26.1065673828125,
        "26": 100.0,
        "1025": -195.74267578125,
        "29": 0.0,
        "1028": 0.0,
        "1032": 0.0,
        "1031": 0.0,
        "1029": 0.0,
        "25": 0.0,
        "27": 0.0,
        "1280": 70,
        "1283": 0,
        "1282": 0,
        "1287": 40,
        "1281": 45,
        "1288": 40,
        "2048": 0
      }
    }
  }
}


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

on_change filter doesn't play nicely with on-device parameter updates

Is there an existing issue for this?

  • I have searched the existing issues

I'm having the following issue:

When subscribing to a parameter update using on_change filter, the callback doesn't get called when parameter is changed on device.

My environment is:

- OS: Windows 11 Pro (WSL 2.0.9)
- Python: Python 3.11.5

I have following devices connected:

  • ecoMAX 3xx series
  • ecoMAX 8xx series
  • ecoMAX 9xx series
  • Expansion module B
  • Expansion module C
  • ecoSTER 200/ecoSTER Touch
  • ecoLAMBDA
  • ecoNET 300

I'm connecting to my devices using:

Ethernet/WiFi to RS-485 converter

I'm seeing following log messages:

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct

Error in parsin device parameters

I get contantly this error:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/pyplumio/connection.py", line 107, in _process
device.set_data(frame.data)
File "/usr/local/lib/python3.9/site-packages/pyplumio/frame.py", line 99, in data
self.parse_message(self.message)
File "/usr/local/lib/python3.9/site-packages/pyplumio/responses.py", line 218, in parse_message
param_type.unpack(message[offset:])
File "/usr/local/lib/python3.9/site-packages/pyplumio/data_types.py", line 45, in unpack
self._data = data[0 : self.size]
File "/usr/local/lib/python3.9/site-packages/pyplumio/data_types.py", line 321, in size
return len(self.value) + 1
File "/usr/local/lib/python3.9/site-packages/pyplumio/data_types.py", line 312, in value
while not self._data[offset] == "\x00":
AttributeError: 'String' object has no attribute '_data'

It appears in library and affects HASS as well. It seems that for my device Ecomax920 the parameters are incorrectly detected and the libarary tries to unpack the parameters from messages wrong. In this case it the last parameter in the schema and message only has left three zero bytes while it tries to decode string. This requires according tho the code thou at least four bytes. Interestingly sometimes i dont see it. But thats only occasionally. It seems the messages from the device change somehow.

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.