google / python-lakeside

Apache License 2.0
45 stars 18 forks source link

Broken pipe on send_packet #7

Closed dclobato closed 6 years ago

dclobato commented 6 years ago

Using lakeside on Home Assistant and, after updating firmware for bulbs T1012, there are several broken pipe errors on bulb update. For example:

Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 196, in async_update_ha_state
    yield from self.async_device_update()
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 319, in async_device_update
    yield from self.hass.async_add_job(self.update)
  File "/usr/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3.6/site-packages/homeassistant/components/light/eufy.py", line 63, in update
    self._bulb.update()
  File "/usr/lib/python3.6/site-packages/lakeside/__init__.py", line 166, in update
    response = self.get_status()
  File "/usr/lib/python3.6/site-packages/lakeside/__init__.py", line 112, in get_status
    packet.sequence = self.get_sequence()
  File "/usr/lib/python3.6/site-packages/lakeside/__init__.py", line 108, in get_sequence
    return device.get_sequence(self)
  File "/usr/lib/python3.6/site-packages/lakeside/__init__.py", line 94, in get_sequence
    response = self.send_packet(packet, True)
  File "/usr/lib/python3.6/site-packages/lakeside/__init__.py", line 105, in send_packet
    return device.send_packet(self, packet, response)
  File "/usr/lib/python3.6/site-packages/lakeside/__init__.py", line 69, in send_packet
    self.s.send(encrypted_packet)
BrokenPipeError: [Errno 32] Broken pipe

Something was broken with firmware 3.6 on Eufy?

dclobato commented 6 years ago

Experimenting with lakeside on console, the errors are the same

>>> import lakeside
>>> devices = lakeside.get_devices("xxxxx", "yyyyyy")
>>> bulb = lakeside.bulb(devices[0]["address"], devices[0]["code"], devices[0]["type"])
>>> bulb
<lakeside.bulb object at 0x103f6ae48>
>>> bulb.get_status()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 112, in get_status
    packet.sequence = self.get_sequence()
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 108, in get_sequence
    return device.get_sequence(self)
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 94, in get_sequence
    response = self.send_packet(packet, True)
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 105, in send_packet
    return device.send_packet(self, packet, response)
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 69, in send_packet
    self.s.send(encrypted_packet)
>>> bulb.set_state(power=True, brightness=50, temperature=100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 161, in set_state
    packet.sequence = self.get_sequence()
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 108, in get_sequence
    return device.get_sequence(self)
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 94, in get_sequence
    response = self.send_packet(packet, True)
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 105, in send_packet
    return device.send_packet(self, packet, response)
  File "/Users/dclobato/Downloads/x/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 69, in send_packet
    self.s.send(encrypted_packet)
AttributeError: 'bulb' object has no attribute 's'
mjg59 commented 6 years ago

Ugh. Sounds like they've changed the local network protocol in a firmware update. I'll see if I can figure out what's going on here.

dclobato commented 6 years ago

@mjg59 does Eufy have any sort of development documentation? :)

I am searching for this kind of information for some time (for trying to fix this error by myself) and can't find anything...

mjg59 commented 6 years ago

No, I had to reverse engineer it.

dclobato commented 6 years ago

What I have noticed, while playing with lakeside on console, is that some commands work fine just after connecting to the bulb. If there is no activity (commands sent to the bulb) for sometime, then we start getting errors. Issuing another bulb.conect() does the trick...

Maybe the new firmware introduced some form of timeout for innactive connection?

>>> bulb = lakeside.bulb(devices[7]["address"], devices[7]["code"], devices[7]["type"])
>>> bulb.connect()
>>> bulb.address
'10.0.1.233'
>>> bulb.get_status()
sequence: 19090895
code: ""
bulbinfo {
  type: 2
  packet {
    unknown1: -1
    bulbstate {
      command: 6
      values {
        brightness: 58
        temperature: 0
      }
      power: 1
      unknown1: 0
      values2 {
        brightness: 58
        temperature: 58
      }
      unknown2: -48
    }
  }
}

>>> bulb.set_state(power=True, brightness=50, temperature=100)
>>> bulb.set_state(power=True, brightness=58, temperature=0)
>>> bulb.set_state(power=True, brightness=58, temperature=100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 161, in set_state
    packet.sequence = self.get_sequence()
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 108, in get_sequence
    return device.get_sequence(self)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 94, in get_sequence
    response = self.send_packet(packet, True)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 105, in send_packet
    return device.send_packet(self, packet, response)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 76, in send_packet
    length = struct.unpack("<H", decrypted_packet[0:2])[0]
struct.error: unpack requires a buffer of 2 bytes
>>> bulb.connect()
>>> bulb.set_state(power=True, brightness=50, temperature=0)
>>> bulb.set_state(power=True, brightness=100, temperature=0)
>>> bulb.set_state(power=True, brightness=50, temperature=0)
>>> bulb.set_state(power=True, brightness=50, temperature=10)
>>> bulb.set_state(power=True, brightness=50, temperature=60)
>>> bulb.set_state(power=True, brightness=50, temperature=100)
>>> bulb.set_state(power=True, brightness=50, temperature=0)
>>> bulb.set_state(power=True, brightness=50, temperature=100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 161, in set_state
    packet.sequence = self.get_sequence()
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 108, in get_sequence
    return device.get_sequence(self)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 94, in get_sequence
    response = self.send_packet(packet, True)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 105, in send_packet
    return device.send_packet(self, packet, response)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 76, in send_packet
    length = struct.unpack("<H", decrypted_packet[0:2])[0]
struct.error: unpack requires a buffer of 2 bytes
>>> bulb.connect()
>>> bulb.set_state(power=True, brightness=50, temperature=100)
>>> bulb.set_state(power=True, brightness=50, temperature=0)
>>> bulb.set_state(power=True, brightness=50, temperature=100)
>>> bulb.set_state(power=True, brightness=50, temperature=100)
>>> bulb.set_state(power=True, brightness=50, temperature=0)
>>> bulb.set_state(power=True, brightness=50, temperature=100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 161, in set_state
    packet.sequence = self.get_sequence()
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 108, in get_sequence
    return device.get_sequence(self)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 94, in get_sequence
    response = self.send_packet(packet, True)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 105, in send_packet
    return device.send_packet(self, packet, response)
  File "/Users/dclobato/Downloads/lakeside/lib/python3.7/site-packages/lakeside/__init__.py", line 76, in send_packet
    length = struct.unpack("<H", decrypted_packet[0:2])[0]
struct.error: unpack requires a buffer of 2 bytes
>>> 
dclobato commented 6 years ago

I have updated lakeside manually on my home assistant installation... The "broken pipe" and "unpack" errors are less frequent, but they are still there, along with a new "connection reset by peer" error

Almost all errors are on self.s.send(encrypted_packet) (lakeside/init.py", line 70) or self.s.connect((self.address, 55556)) (lakeside/init.py", line 57)

mjg59 commented 6 years ago

Can you try with the code from the keepalive branch? I'm afraid I don't have access to any Eufy hardware at the moment, so I can't verify it.

dclobato commented 6 years ago

Can you try with the code from the keepalive branch?

I am running this code for 12h now. I see no disconnect errors. I'll keep it running for one or two more days

The only errors that I see are some "no route to host" when, for some reason, the bulbs are disconnected from wifi and HA try to set/get bulb status; but I think this is, some how, an expected behaviour, because the device is disconnected and there is no way to tell HA to stop/resume polling it

Mon Aug 13 2018 09:11:30 GMT-0300 (Brasilia Standard Time)

Update for light.sbmurilo2 fails
Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 82, in send_packet
    self.s.send(encrypted_packet)
BrokenPipeError: [Errno 32] Broken pipe

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/srv/homeassistant/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 199, in async_update_ha_state
    yield from self.async_device_update()
  File "/srv/homeassistant/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 322, in async_device_update
    yield from self.hass.async_add_job(self.update)
  File "/usr/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/srv/homeassistant/lib/python3.6/site-packages/homeassistant/components/light/eufy.py", line 62, in update
    self._bulb.update()
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 192, in update
    response = self.get_status()
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 138, in get_status
    packet.sequence = self.get_sequence()
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 134, in get_sequence
    return device.get_sequence(self)
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 115, in get_sequence
    response = self.send_packet(packet, True)
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 131, in send_packet
    return device.send_packet(self, packet, response)
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 84, in send_packet
    self.connect()
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 128, in connect
    return device.connect(self)
  File "/srv/homeassistant/lib/python3.6/site-packages/lakeside/__init__.py", line 65, in connect
    self.s.connect((self.address, 55556))
OSError: [Errno 113] No route to host

I believe that can be interesting to add some log/debug messages to the code to help pinpoint these errors...

mjg59 commented 6 years ago

Fixed in https://github.com/google/python-lakeside/commit/b37d46c8686b22517ca28c7091c3af5c6588021d - I think Lakeside is now working as intended, although HA could potentially handle some failures more usefully.