jeffreydwalter / arlo

Python module for interacting with Netgear's Arlo camera system.
Apache License 2.0
520 stars 123 forks source link

('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read)) #139

Closed lennyzaman closed 4 years ago

lennyzaman commented 4 years ago

Please answer these questions before submitting your issue. Thanks!

What version of Python are you using (python -V)?

Python 3.5.3

What operating system and processor architecture are you using (python -c 'import platform; print(platform.uname());')?

uname_result(system='Linux', node='raspberrypi1', release='4.14.98-v7+', version='#1200 SMP Tue Feb 12 20:27:48 GMT 2019', machine='armv7l', processor='')

Which Python packages do you have installed (run the pip freeze or pip3 freeze command and paste output)?

arlo==1.2.32
asn1crypto==0.24.0
astroid==1.4.9
asttokens==1.1.13
atomicwrites==1.3.0
attrs==19.1.0
automationhat==0.1.0
blinker==1.3
blinkt==0.1.2
buttonshim==0.0.2
Cap1xxx==0.1.3
certifi==2019.6.16
cffi==1.12.3
chardet==3.0.4
click==6.6
colorama==0.3.7
colorzero==1.1
cryptography==2.7
Django==2.2.5
docutils==0.13.1
drumhat==0.1.0
EasyProcess==0.2.7
envirophat==1.0.0
ExplorerHAT==0.4.2
Flask==0.12.1
fourletterphat==0.1.0
future==0.17.1
gevent==1.4.0
gpiozero==1.5.0
greenlet==0.4.15
idna==2.8
image==1.5.27
importlib-metadata==0.23
influxdb==5.2.2
isort==4.2.5
itsdangerous==0.24
jedi==0.10.2
Jinja2==2.8
keyring==10.1
keyrings.alt==1.3
lazy-object-proxy==1.2.2
MarkupSafe==0.23
mcpi==0.1.1
microdotphat==0.2.1
monotonic==1.5
more-itertools==7.2.0
mote==0.0.4
motephat==0.0.2
mypy==0.470
numpy==1.12.1
oauthlib==2.0.1
packaging==19.2
paho-mqtt==1.4.0
pantilthat==0.0.7
pathlib2==2.3.4
pgzero==1.2
phatbeat==0.1.1
pianohat==0.1.0
picamera==1.13
picraft==1.0
piglow==1.2.4
pigpio==1.38
Pillow==6.1.0
pluggy==0.13.0
py==1.8.0
pyarlo==0.2.3
pyasn1==0.1.9
pycparser==2.19
pycrypto==2.6.1
pygame==1.9.3
Pygments==2.2.0
pygobject==3.22.0
pyinotify==0.9.6
PyJWT==1.4.2
pylint==1.6.5
pyOpenSSL==16.2.0
pyparsing==2.4.2
pyperclip==1.5.27
pyserial==3.2.1
PySocks==1.7.1
pytesseract==0.3.0
pytest==5.1.2
python-apt==1.1.0b5
python-dateutil==2.8.0
python-telegram-bot==12.0.0b1
pytz==2019.2
PyVirtualDisplay==0.2.4
pyxdg==0.25
rainbowhat==0.1.0
requests==2.21.0
requests-oauthlib==0.7.0
roman==2.0.0
RPi.GPIO==0.6.5
RTIMULib==7.2.1
schedule==0.6.0
scrollphat==0.0.7
scrollphathd==1.2.1
SecretStorage==2.3.1
selenium==2.53.5
sense-emu==1.1
sense-hat==2.2.0
simplejson==3.10.0
six==1.12.0
skywriter==0.0.7
sn3218==1.2.7
spidev==3.3
sqlparse==0.3.0
sseclient==0.0.22
ssh-import-id==5.6
thonny==3.1.0
thonny-pi==1.1
tornado==6.0.3
touchphat==0.0.1
twython==3.4.0
typed-ast==0.6.3
unicornhathd==0.0.4
urllib3==1.24.1
wcwidth==0.1.7
webcolors==1.3
websocket==0.2.1
Werkzeug==0.11.15
wrapt==1.9.0
xvfbwrapper==0.2.9
zipp==0.6.0

Which version of ffmpeg are you using (ffmpeg -version)?

not applicable

Which Arlo hardware do you have (camera types - [Arlo, Pro, Q, etc.], basestation model, etc.)?

Arlo Ultra (1 basestation, 2 cameras)

What did you do?

If possible, provide the steps you took to reproduce the issue. A complete runnable program is good. (don't include your user/password or any sensitive info)

#!/usr/bin/env python
# Arlo server (C) Lenny Zaman
# Version 0.1.0

import sys
from arlo import Arlo
from threading import Thread
import paho.mqtt.client as mqtt
import requests,logging, ast, time, datetime, threading, pprint, schedule, random
from queue import Queue

if sys.platform == "linux":
    log_path = "######"

else:
    log_path = ""

# globals
arlo_modes={}
# mqtt Queue
mqtt_q = Queue()
# logging setup
default_log_level = 19
logging.basicConfig(
        filename='{}Arlo_client.log'.format(log_path),
        level=default_log_level,
        datefmt="%Y-%m-%d %H:%M:%S",
        format="%(asctime)s.%(msecs)03d:%(filename)s:%(levelname)8s:%(funcName)s:%(lineno)d: %(message)s"
        )

logging.info('*********** Arlo Python Client ************'.format())
if sys.platform == "linux":
    logging.log(19,"Linux platform")
else:
    logging.log(19,"Other platform")

# MQTT parameters
mqtt_qos = 0
mqtt_retained = True

USERNAME = '#####'
PASSWORD = '#####'

# MQTT connection
def mqtt_conn():

    # MQTT
    # The callback for when the client receives a CONNACK response from the server.
    def on_connect(client, userdata, flags, rc):

        if rc == 0:
            logging.info('Connected!! to MQTT result code: RC:  {:s}!'.format(str(rc)))
        elif rc == 1:
            logging.info('Connection refused – incorrect protocol version: RC:  {:s}!'.format(str(rc)))
        elif rc == 2:
            logging.info('Connection refused – invalid client identifier: RC:  {:s}!'.format(str(rc)))
        elif rc == 3:
            logging.info('Connection refused – server unavailable: RC:  {:s}!'.format(str(rc)))
        elif rc == 4:
            logging.info('Connection refused – bad username or password: RC:  {:s}!'.format(str(rc)))
        elif rc == 5:
            logging.info('Connection refused – not authorised: RC: {:s}!'.format(str(rc)))
        ###
        # subscribe to the homie topic
        client.subscribe("homie/#", qos=1)
        logging.debug("Succesfully subscribed to the homie/ topic")
    mqtt_id_random = random.randint(0,999)
    mqttc = mqtt.Client(client_id="Arlo_updater_{}".format(mqtt_id_random))
    mqttc.username_pw_set(username="######",password="######") 
    mqttc.on_connect = on_connect
    #mqttc.on_message = on_message
    mqttc.loop_start() 
    mqtt_alive = False
    while mqtt_alive == False:
        try:
            mqttc.connect("############")
            mqtt_alive = True
            logging.info("MQTT connection established".format())
        except:
            mqtt_alive = False
            logging.info("MQTT connection failed, retry".format())
            time.sleep(1)

    time.sleep(1)

    return mqttc

try:
    # instantiate mqtt connections
    mqttc = mqtt_conn()
    print("MQTT connected")
    logging.log(19,"MQTT success")

    # Instantiating the Arlo object automatically calls Login(), which returns an oAuth token that gets cached.
    # Subsequent successful calls to login will update the oAuth token.
    arlo = Arlo(USERNAME, PASSWORD)
    logging.log(19,"Connected and logged in to Arlo")
    # At this point you're logged into Arlo.

    # Get the list of devices and filter on device type to only get the basestation.
    # This will return an array which includes all of the basestation's associated metadata.
    basestations = arlo.GetDevices('basestation')
    logging.debug("Got Deviclist")
    logging.debug(str(basestations))

    # Define a callback function that will get called once for each motion event.
    def callback(arlo, event):
        CAMIDS = {"##########":"Arlo_Living_Camera","#############":"Arlo_Tuin_Camera"}
        # Here you will have access to self, basestation_id, xcloud_id, and the event schema.
        print("event detected!")
        print(event)
        mqttc.publish("homie/Arlo_feed/raw_feed",str(event),0,False)
        if 'resource' in event:
            # this is when I request camera statesbattery level and ON/OFF mode
            if event['resource'] == 'cameras': 
                for camstate in event['properties']:

                    CAM_ID = CAMIDS[camstate['serialNumber']]
                    charge_state = "OFF" if camstate['chargerTech']=="None" else "ON"
                    mqtt_q.put({"cams":{"camid":CAM_ID,"batlevel":camstate['batteryLevel'],"mode":"OFF" if camstate['privacyActive'] else "ON","charging":charge_state}})

                #print(event['properties'][0]['batteryLevel'],event['properties'][1]['batteryLevel'])
            # this is when streams on Living Cam startup (Streammode)
            try:
                CAM_ID = CAMIDS[event['resource'].split("/")[1]]

                if 'properties' in event:

                    if 'activityState' in event['properties']:
                        if event['properties']['activityState'] == "idle":
                            print("{} is OFF".format(CAM_ID))

                        if event['properties']['activityState'] == "userStreamActive":
                            print("{} is streaming".format(CAM_ID))

                        if event['properties']['activityState'] == "startUserStream":
                            print("{} is starting to streaming".format(CAM_ID))
                        mqtt_q.put({"cams":{"camid":CAM_ID,"activityState":event['properties']['activityState']}})

                    if 'motionDetected' in event['properties']:
                        mqtt_q.put({"cams":{"camid":CAM_ID,"motion":"ON" if event['properties']['motionDetected'] else "OFF"}})

                    if 'privacyActive' in event['properties']:
                        mqtt_q.put({"cams":{"camid":CAM_ID,"mode":"OFF" if event['properties']['privacyActive'] else "ON"}})
                    if 'chargerTech' in event['properties']:
                        mqtt_q.put({"cams":{"camid":CAM_ID,"charging":"OFF" if event['properties']['chargerTech']=="None" else "ON"}})
            except Exception as e:
                print(e)
                pass

            # queried the modes got the data
            if event['resource'] == "modes":
                if 'activeMode' in event['properties']:
                    print("received activemode")
                    mqtt_q.put({"activemode":arlo_modes[event['properties']['activeMode']]})
                else:
                    print("received modes")
                    activemode = event['properties']['active']
                    for mode in event['properties']['modes']:
                        if 'type' in mode:
                            modename = mode['type']
                        else:
                            modename = mode['name']
                        arlo_modes[mode['id']] = modename
                    mqtt_q.put({"modes":arlo_modes})
                    mqtt_q.put({"activemode":arlo_modes[activemode]})
                #pprint.pprint(event['properties'])

        if 'properties' in event:
            if 'motionDetected' in event['properties']:
                print(event)

        if 'status' in event and not 'resource' in  event:
            print("Status Call",event['status'])
        #mqttc.publish("homie/Arlo_test","TEST",0,False)
        print("\n-----------\n***********")

    # Subscribe to all events. 

    event_t = Thread(target=arlo.HandleEvents,args=(basestations[0],callback,))
    event_t.start()
    logging.log(19,"Event thread started")

    def get_openhab_data(arlo,bs):
        # trigger battery states to be updated
        arlo.Ping(bs)
        logging.log(19,"sent a Ping")
        time.sleep(2)
        arlo.Notify(bs, {"action":"get","resource":"cameras","publishResponse":False})
        arlo.Notify(bs, {"action":"get","resource":"modes","publishResponse":False})
        #arlo.GetDevices('camera')
        print("Next update {}".format(schedule.next_run()))
        logging.log(19,"Next run at {}".format(schedule.next_run()))

    def mqtt_handler(mqtt_q):
        while True:
            data = mqtt_q.get()

            if "cams" in data:

                if 'activityState' in data['cams']:
                    if data['cams']['activityState'] == "userStreamActive": activityState = "ON"  
                    else: activityState = "OFF"

                    mqttc.publish('homie/'+data['cams']['camid']+'/streaming',activityState,0,True)
                if 'batlevel' in data['cams']:
                    mqttc.publish('homie/'+data['cams']['camid']+'/battery',data['cams']['batlevel'],0,True)
                if 'mode' in data['cams']:
                    mqttc.publish('homie/'+data['cams']['camid']+'/mode',data['cams']['mode'],0,True)
                if 'charging' in data['cams']:
                    mqttc.publish('homie/'+data['cams']['camid']+'/charge_state',data['cams']['charging'],0,True)
                if 'motion' in data['cams']:
                    mqttc.publish('homie/'+data['cams']['camid']+'/motion',data['cams']['motion'],0,True)
            if 'activemode' in data:
                mqttc.publish("homie/Arlo_Basestation/mode",data['activemode'],0,True)

            print(datetime.datetime.now(),data)
            logging.log(19,data)

            time.sleep(0.1)
    mqtt_t = Thread(target=mqtt_handler,args=(mqtt_q,))
    mqtt_t.start()
    logging.log(19,"mqtt handler thread started")
    time.sleep(1)
    get_openhab_data(arlo,basestations[0])
    logging.log(19,"called the data getter to intialise values  ")

    schedule.every(15).minutes.do(get_openhab_data,arlo,basestations[0])
    logging.log(19,"scheduled the data getter function call")

    print("Thread started succesfully, program continued")
    while True:
        schedule.run_pending()
        time.sleep(1)

except Exception as e:
    print(e)

What did you expect to see?

A continuous capture every 15' of the parameters I like to update (i.E camera battery state, chargerTech state etc)
To get this I use the Notify function with 'cameras' and 'modes' as body, this triggers events onto the eventstream to which I subscribe in a different thread.
It works for a while but after a certain time I get  timeouts on the connection 

What did you see instead?

-----> LAST correct output, then the following: repeating at an certain interval
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection aborted.', OSError("(110, 'ETIMEDOUT')",))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))

Does this issue reproduce with the latest release?

yes

Hi Jeffrey, First off thanks for the great work on this tool. To make it all a bit clear what I want to have is a link between arlo and my openhab. I use MQTT to output all the data to that bus. To get my data I subscribe to the EventStream with 'HandleEvents' in a separate thread. I process all these events and selectively send them on into a Queue for handling(into MQTT) To get regular updates I use 'schedule' to run 2 function calls arlo.Notify() with "cameras" and "modes" to force arlo to send me the current data. I also added a Ping() call in this scheduled run but alas... Sorry if the code is inconsistent. I'm pretty new to programming and this is still a WIP...

If you need more info, please...

Thanks, Best regards!

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

slobglob commented 4 years ago

Hey,

the same happens to me. I'm using the startStream functionality which triggers subscribe+notify, once on of the notify requests crashes:

DEBUG:urllib3.connectionpool:https://my.arlo.com:443 "POST /hmsweb/users/devices/notify/51D2867VA0528 HTTP/1.1" 200 None
('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))
DEBUG:urllib3.connectionpool:Resetting dropped connection: my.arlo.com
DEBUG:urllib3.connectionpool:https://my.arlo.com:443 "GET /hmsweb/client/subscribe?token=2_5VRtWbKukXO79-EySXRFp7DROIJnkiNqn4Ff65-wTvER6ln5lPH_cGc0wFSIL8JvnGJC8mTSxZw1oSO41QNXCTX1n0Exbvs02-t53zV4zVrTlfMDTD6YsUf7PhZ0yatXlmbqtowrW-YNbf5wi_vJqS5kqySTIt3BP8jP4xUuXXIH HTTP/1.1" 200 None

Then the Notify isn't called anymore and the stream brakes. The described behavior is inconsistent so it's hard to reproduce and fix. Any idea? @lennyzaman can you reopen this?

slobglob commented 4 years ago

Still an issue

jeffreydwalter commented 4 years ago

If someone could provide a slimmed down script that reproduces the issue without all of the mqtt stuff, I'd be happy to try to get to the bottom of it.

slobglob commented 4 years ago

Hey @jeffreydwalter . I was able to reproduce it constantly with the Arlo Go, maybe it'll make some sense.

  1. Create an Arlo object
  2. Start stream
  3. /Notify gets called constantly
  4. After 1 hour the camera goes idle / sleep mode.
  5. Then you'll start seeing this logs of Incomplete Read and Resetting dropped connection

I'm not sure if it's exactly the same issue as described above but maybe it'll help to connect the dots.

Thanks.

apsteinmetz commented 4 years ago

I have the same issue using your snapshot template wrapped in a 'while True:' loop. It is the result of trying to instantiate the arlo object (WRONG: See update). The first run through goes fine. Then the loop waits for an interval of time to pass before reconnecting and snapping another shot. I get the "Connection broken:" notice twice but then it connects okay. Then it happens with every subsequent interval (30 minutes in my case). Perhaps arlo.logout needs to be explicity called after getting my business done? Let me try that. I am a Python noob.

UPDATE: I see the "Connection broken" message outside of daylight hours so it's happening outside of the take_snapshot() function which instantiates the object. So I'm guessing that the connection is not explicitly closed (logout?) and times out from inactivity. Fortunately it doesn't stop a new connection when the snapshot time comes around again.

Yet Another UPDATE: Yes. 'arlo.Logout()' before the script goes to sleep again stops those messages from popping up.

Here is the stripped down code:

# -*- coding: utf-8 -*-
"""
Created on Wed Jan 15 19:44:54 2020

@author: Arthur
"""
from arlo import Arlo

import datetime
import time
import sys

interval_minutes = 30
sunset_hour = 19
sunrise_hour = 6
USERNAME = 'blah@blah.com'
PASSWORD = 'xxxxxxxxxx'

def take_snapshot():
    camera_num = 1
    try:
        arlo = Arlo(USERNAME, PASSWORD)
        basestations = arlo.GetDevices('basestations')
        cameras = arlo.GetDevices('cameras')
        snapshot_url = arlo.TriggerFullFrameSnapshot(basestations[0], cameras[camera_num])
        snapshot_filename = cameras[camera_num]['deviceName'] + '-' + time.strftime('%Y-%m-%d_%H-%M-%S',
                                                                                    time.localtime()) + '.jpg'
        arlo.DownloadSnapshot(snapshot_url, 'snapshots/' + snapshot_filename)
       #  UPDATE Addition
        arlo.Logout()  #added this to fix 'Connection Broken' timeout message
    except Exception as e:
        return repr(e)
    return "Success"

if __name__ == "__main__":
    dt = datetime.datetime.now()
    while True:
        hour = datetime.datetime.now().hour
        if sunrise_hour < hour < sunset_hour:
            result = take_snapshot()

        dt = datetime.datetime.now() + datetime.timedelta(minutes=interval_minutes)
        while datetime.datetime.now() < dt:
            time.sleep(1)

('Linux', 'Homeserver', '5.0.0-32-generic', '#34~18.04.2-Ubuntu SMP Thu Oct 10 10:36:02 UTC 2019', 'x86_64', 'x86_64')

Python 3.6.9

$pip3 freeze apt-clone==0.2.1 apturl==0.5.2 arlo==1.2.33 beautifulsoup4==4.6.0 Brlapi==0.6.6 certifi==2019.11.28 chardet==3.0.4 command-not-found==0.3 configobj==5.0.6 cupshelpers==1.0 defer==1.0.6 httplib2==0.9.2 idna==2.8 louis==3.5.0 macaroonbakery==1.1.3 Mako==1.0.7 MarkupSafe==1.0 monotonic==1.5 nemo-emblems==4.4.0 netifaces==0.10.4 onboard==1.4.1 PAM==0.4.2 pexpect==4.2.1 Pillow==5.1.0 protobuf==3.0.0 psutil==5.4.2 pycairo==1.16.2 pycrypto==2.6.1 pycups==1.9.73 pycurl==7.43.0.1 pygobject==3.26.1 PyICU==1.9.8 pyinotify==0.9.6 pymacaroons==0.13.0 PyNaCl==1.1.2 pyparted==3.11.1 pyRFC3339==1.0 PySocks==1.7.1 python-apt==1.6.4 python-debian==0.1.32 python-xapp==1.8.1 python-xlib==0.20 pytz==2018.3 pyxdg==0.25 PyYAML==3.12 reportlab==3.4.0 requests==2.22.0 requests-unixsocket==0.1.5 sessioninstaller==0.0.0 setproctitle==1.1.10 six==1.14.0 sseclient==0.0.22 system-service==0.3 systemd-python==234 tinycss==0.4 ubuntu-drivers-common==0.0.0 ufw==0.36 urllib3==1.25.7 virtualenv==16.7.9 xkit==0.0.0

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.