rcastberg / sure_petcare

Python library for accessing sure connect petflap
GNU General Public License v3.0
55 stars 15 forks source link

Suggestion: lock in cat ("indoor cat")/ free cat ("outdoor cat") #16

Open agreulich opened 3 years ago

agreulich commented 3 years ago

great project - is it still maintained (2 years old)?

My suggestion is to add code that allows to modify a cat to an "indoor cat", which locks them in, and back to an "outdoor cat", e.g. to keep one cat inside while others can leave (because of being sick, pending vet visit, etc). The app and web frontend allows to do this, but unfortunately without a timer; it is not possible in the app to say "modify cat x to indoor cat at 3am", so the cat can still enter and leave freely until then, but after this, needs to stay inside (because, e.g., a vet visit in the morning), while other cats - I have 3 - can enter and leave freely. Moreover, the feature is hidden deep inside the app and I originally had trouble finding it.

Actually I've already written code that works; feel free to add it in a way that you seem fit, if you like. I did not (yet) figure out how to read the status, because my priority was setting it. Of course you might decide against it, because this involves writing data to the server instead of only readin data.

The main modification is to add a method in class SurePetFlapMixin (line 684 of __init__.py) like this:

    def set_pet_profile( self, pet_id = None, name = None, profile = None, household_id = None ):
        """
        Set lock mode of a pet (3 is locked, 2 is free)
        """
        household_id = household_id or self.default_household
        if profile is None or type(profile) != int:
            raise ValueError('Please define a profile int value')
        if pet_id is None and name is None:
            raise ValueError('Please define pet_id or name')
        if pet_id is None:
            pet_id = self.get_pet_id_by_name(name)
        pet_id = int(pet_id)
        try:
            tag_id = self.household['pets'][pet_id]['tag_id']
            pet_name = self.household['pets'][pet_id]['name']
        except KeyError as e:
            raise SPAPIUnknownPet(str(e))
        device_id = self.household['default_flap']

        headers = self._create_header()
        data = {"profile": profile}
        url = '%s/%s/tag/%s' % (_URL_DEV, device_id, tag_id)
        response = self.s.put(url, headers=headers, json=data)
        if response.status_code == 401:
            self.update_authtoken(force=True)
            if 'headers' in kwargs and 'Authorization' in kwargs['headers']:
                kwargs['headers']['Authorization'] = 'Bearer ' + self.cache['AuthToken']
                response = self.s.post(_URL_AUTH, headers=headers, json=data)
            else:
                raise SPAPIException('Auth required but not present in header')

        response_data = response.json()
        return 'data' in response_data and 'profile' in response_data['data'] and \
               response_data['data']['profile'] == profile

And adding to sp_cli.py

@cmd
def cmd_pet_lock( sp: sure_petcare.SurePetFlapMixin, args ):
    """
    For each pet in household, lock it inside
    """
    try:
        name = args.cmd[1]
    except IndexError:
        exit('need pet name (enclose in quotes if necessary)')

    if sp.set_pet_profile( name = name, profile=3 ):
        print("ok")
    else:
        print("fail")

@cmd
def cmd_pet_free( sp: sure_petcare.SurePetFlapMixin, args ):
    """
    For each pet in household, lock it inside
    """
    try:
        name = args.cmd[1]
    except IndexError:
        exit('need pet name (enclose in quotes if necessary)')

    if sp.set_pet_profile( name = name, profile=2 ):
        print("ok")
    else:
        print("fail")

You might want to rename things and/or change code style. It basically PUTs to an URL .../device/<flap-id>/tag/<pet-tag-id> with a json {"profile": 3} to lock in, and {"profile": 2} to free (I don't know if other profile values might have different effects).

PS: It is possible to read the status via URL _URL_DEV and parameters to

        params = (
            ('with[]', 'tags'),
        )

The last record in datalist then has a tagslist (of all pets), each with id== pet tag-id and profile set to 2 or 3 (plus update timestamp in updated_at)

agreulich commented 3 years ago

I added reading mode to get_current_status via get_pet_location and modifying update_pet_status plus some constants. I attach __init__.pyand sp_cli.py below and tried to keep your coding stlye, thought it might have failed at places :-)

Example:

$ python sp_cli.py ls_pets
cat1 (111111) is Free to leave (outdoor pet) and currently Outside
cat2 (222222) is Free to leave (outdoor pet) and currently Outside
cat3 (333333) is Free to leave (outdoor pet) and currently Outside

$ python sp_cli.py pet_lock cat3
ok

$ python sp_cli.py --update
$ python sp_cli.py ls_pets
cat1 (111111) is Free to leave (outdoor pet) and currently Outside
cat2 (222222) is Free to leave (outdoor pet) and currently Outside
cat3 (333333) is Locked in (indoor pet) and currently Outside

$ python sp_cli.py pet_free cat3
ok

$ python sp_cli.py --update
$ python sp_cli.py ls_pets
cat1 (111111) is Free to leave (outdoor pet) and currently Outside
cat2 (222222) is Free to leave (outdoor pet) and currently Outside
cat3 (333333) is Free to leave (outdoor pet) and currently Outside

I created a pull request for this.

rcastberg commented 3 years ago

Hi,

I havn't done much with this project since family and job changes happened, i will try to look into this soon.