Page 9 of 17

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Saturday 23 November 2019 23:32
by traolo
philchillbill wrote: Saturday 23 November 2019 18:13 Oh boy. One of those days. Sorry. Does it work now?

Edit: I see the dev branch in the original post. Not sure why you think I linked the master.
Ops. I cloned the master branch.
I updated the scripts but receiving another error:
File "./reportstate.py", line 113, in main
data = AlexaSmartHome.async_changereport(endpoint, token)
AttributeError: module 'AlexaSmartHome' has no attribute 'async_changereport'

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Sunday 24 November 2019 8:53
by philchillbill
It’s called api_reportState in the original.


Sent from my iPhone using Tapatalk

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Sunday 24 November 2019 12:19
by traolo
philchillbill wrote: Sunday 24 November 2019 8:53 It’s called api_reportState in the original.


Sent from my iPhone using Tapatalk
Hello, thanks for your support. I did the test yesterday but getting this error:
1)
File "./reportstate.py", line 113, in main
data = AlexaSmartHome.api_reportState(endpoint, token)
File "/home/pi/domoticz/scripts/python/alexa/AlexaSmartHome.py", line 745, in api_reportState
properties.extend(interface.serializeProperties())
File "/home/pi/domoticz/scripts/python/alexa/AlexaSmartHome.py", line 138, in serializeProperties
prop_value = self._endpoint.getProperty(prop_name)
AttributeError: 'Domoticz' object has no attribute 'getProperty'

Plus I uploaded on aws the AlexaSmartHome.py and the DomoticzHandler.py included in the dev branch and reloaded the skill but then Alexa is not able to handle the devices on domoticz:

2)
Function Logs:
START RequestId: 613a7bfc-a5bd-4a02-9e8b-c7cccb629723 Version: $LATEST
Traceback (most recent call last):
File "/var/task/AlexaSmartHome.py", line 334, in invoke
return operator.attrgetter(name)(self)(request)
File "/var/task/AlexaSmartHome.py", line 372, in TurnOn
endpoint = self.handler.getEndpoint(request)
File "/var/task/DomoticzHandler.py", line 400, in getEndpoint
endpoint = getEndpointById(request['endpoint']['endpointId'])
NameError: name 'getEndpointById' is not defined
END RequestId: 613a7bfc-a5bd-4a02-9e8b-c7cccb629723

To bypass the first error I changed the properties.extend(interface.serializeProperties()) in properties.append(interface.serializeProperties())

For the second error I updated the line 400 of the DomoticzHandler.py script:
endpoint = self.getEndpointById(request['endpoint']['endpointId'])

And now I'm receiving this error from the reportstate.py script:
Traceback (most recent call last):
File "./reportstate.py", line 122, in <module>
main(sys.argv[1:])
File "./reportstate.py", line 117, in main
response = requests.post(ALEXA_URI, headers=headers, json=data)
File "/usr/lib/python3/dist-packages/requests/api.py", line 110, in post
return request('post', url, data=data, json=json, **kwargs)
File "/usr/lib/python3/dist-packages/requests/api.py", line 56, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/lib/python3/dist-packages/requests/sessions.py", line 474, in request
prep = self.prepare_request(req)
File "/usr/lib/python3/dist-packages/requests/sessions.py", line 407, in prepare_request
hooks=merge_hooks(request.hooks, self.hooks),
File "/usr/lib/python3/dist-packages/requests/models.py", line 305, in prepare
self.prepare_body(data, files, json)
File "/usr/lib/python3/dist-packages/requests/models.py", line 445, in prepare_body
body = complexjson.dumps(json)
File "/usr/lib/python3.5/json/__init__.py", line 230, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python3.5/json/encoder.py", line 198, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.5/json/encoder.py", line 256, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python3.5/json/encoder.py", line 179, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <generator object AlexaInterface.serializeProperties at 0xb6981240> is not JSON serializable

So I'm able to get a valid access token but the body of the request is not a json, I guess because it is mandatory to extend the properties array instead of append. Maybe the first error is due to an error with the definition of the contact sensor in domoticz?

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Sunday 24 November 2019 20:02
by philchillbill
I'll take a proper look at this tomorrow. Maybe the easiest thing to do it to just create a single file for reportstate.py that has all the stuff it needs from the imports locally so it does not depend on external files. Sorry for this. I extended my Alexicz to do way more than just Domoticz from within a single skill and that's why my code is now so different.

But the AcceptGrant code does work ok for you? That will always be needed in the Lambda.

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Sunday 24 November 2019 23:00
by traolo
philchillbill wrote: Sunday 24 November 2019 20:02 I'll take a proper look at this tomorrow. Maybe the easiest thing to do it to just create a single file for reportstate.py that has all the stuff it needs from the imports locally so it does not depend on external files. Sorry for this. I extended my Alexicz to do way more than just Domoticz from within a single skill and that's why my code is now so different.

But the AcceptGrant code does work ok for you? That will always be needed in the Lambda.
Thank you! I updated the AlexaSmartHome.py deployed in the Lambda as you suggested and it works great. Linking the skill with my account, I'm able to obtain the CODE.

I guess the issue I'm having is related to the error:
File "/home/pi/domoticz/scripts/python/alexa/AlexaSmartHome.py", line 745, in api_reportState
properties.extend(interface.serializeProperties())
File "/home/pi/domoticz/scripts/python/alexa/AlexaSmartHome.py", line 138, in serializeProperties
prop_value = self._endpoint.getProperty(prop_name)
AttributeError: 'Domoticz' object has no attribute 'getProperty'

I changed the array extend method with the append to force the creation of the request but if you look at the result you will see it is not a well-formatted json due to the properties array build from the properties.extend(interface.serializeProperties()) called by the api_reportState:

{'event': {'payload': {'change': {'cause': {'type': 'PHYSICAL_INTERACTION'}, 'properties': [<generator object AlexaInterface.serializeProperties at 0xb6971210>, <generator object AlexaInterface.serializeProperties at 0xb619f450>]}}, 'endpoint': {'scope': {'type': 'BearerToken', 'token': 'Atza|XXXXXXXXXXXX'}, 'endpointId': 'Contact-115'}, 'header': {'name': 'ChangeReport', 'namespace': 'Alexa', 'payloadVersion': '3', 'messageId': 'XXXX-XXXX_XXX-XXXXX'}}}

So I think the extend method is necessary for the right construction of the properties array, but in my case maybe the method is failing due to the definition of my contact device in domoticz. Maybe some missing information.

I will wait for your hints :-)

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 8:55
by philchillbill
Remember that, as per https://developer.amazon.com/docs/devic ... faces.html, only the en-US and en-CA locales support contact and motion sensors. What locale are you using for Alexicz?

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 11:11
by traolo
philchillbill wrote: Monday 25 November 2019 8:55 Remember that, as per https://developer.amazon.com/docs/devic ... faces.html, only the en-US and en-CA locales support contact and motion sensors. What locale are you using for Alexicz?
On https://developer.amazon.com/alexa/console/ for smart home I'm using it-IT locale. Do I need to switch to en-US? In that case, the Alexa will accept commands in Italian?

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 12:22
by philchillbill
Contact sensors and Motion sensors only work in US English, not even British. So Italian is really a no-no, I'm afraid.

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 13:03
by traolo
philchillbill wrote: Monday 25 November 2019 12:22 Contact sensors and Motion sensors only work in US English, not even British. So Italian is really a no-no, I'm afraid.
Ok, let's work to anticipate amazon when the feature will be available in Europe then! :-D
Do you have an example of the right request?

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 13:48
by philchillbill
You mean to request Amazon to implement it? They already know people want it - we are not the first requesting this.

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 13:59
by traolo
philchillbill wrote: Monday 25 November 2019 13:48 You mean to request Amazon to implement it? They already know people want it - we are not the first requesting this.
No I'm referring to the body request created by the api_reportState. To understand why it is failing

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 21:21
by tmh88
Evening all, im currently seting up the alexa intergration and folowing the guide im upto step. 5.5. Click « Author from scratch » (1)  Set a name for your function « alexicz » (2)  Choose the
runtime « Python 3.6 » (3)  Select the « Create a custom role » as role option (4)
but im unable to find the create a custom role option!
please can someone help??
Thanks in advance

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Monday 25 November 2019 23:16
by tmh88
Continuation from above, I believe I have sorted the issue above I’m now testing through the lambda console using the "discovery" code provided in the manual but I’m getting an error of "urllib.error.URLError: <urlopen error EOF occurred in violation of protocol (_ssl.c:852)>"

Any ideas on this one? Thanks

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Tuesday 26 November 2019 20:27
by philchillbill
No I'm referring to the body request created by the api_reportState. To understand why it is failing
My request looks like this:

{'event': {'header': {'namespace': 'Alexa', 'name': 'ChangeReport', 'messageId': 'bd292671-b49a-46b6-8664-3083a5a29d1e', 'payloadVersion': '3'}, 'payload': {'change': {'cause': {'type': 'PHYSICAL_INTERACTION'}, 'properties': [{'name': 'connectivity', 'namespace': 'Alexa.EndpointHealth', 'value': {'value': 'OK'}, 'timeOfSample': '2019-11-26T19:25:05Z', 'uncertaintyInMilliseconds': 100}, {'name': 'detectionState', 'namespace': 'Alexa.ContactSensor', 'value': 'DETECTED', 'timeOfSample': '2019-11-26T19:25:05Z', 'uncertaintyInMilliseconds': 100}]}}, 'endpoint': {'scope': {'type': 'BearerToken', 'token': 'Atza|IwEBILZjl3KODq-KViz7-ZiXjkYpFgE-guu48kJm7njOicP9ESq7EY4zVPu8NR0FHfviB5fMWVwOGqy-j4AbeW5B_-bhzB1QJOCrm2SAqupP3ehnh_Q8o64t9pfwyVtAMCcABFN1cI5tQDzDlXT5PiSqjVTx8F9yjgk-3nP9fbE6fMmucoht5pMaApedzLheNTXQGx9-N0VNIj_gSe6uheYkNSSolZjMzBTJvTn2924iBdjcXOms8282289_W4VlMwuL8HELz5onKrwRJHMobcMCJJPsS3F41GmMlwV3mqN0Tm-IvQEa3-43RM2Zf4MPspdw1MY9tNFrs4cogLXdBnuZTr0_TIOLTtQp_21o-rq-_x9dOg'}, 'endpointId': 'Contact-356'}}, 'context': {}}

It should be trivial to write a few lines of code inside reportstate.py that create such a piece of JSON to post away. That way, no need at all for the imports from the lambda.

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Tuesday 26 November 2019 21:17
by philchillbill
OK, success ! This standalone script does not rely on the python files from the Lambda and does it all self-contained. I've tested and verified it works if you use the correct 3 parameters.We no longer need the local Domoticz IP address in the top of the file.

Example usage: python reportstate.py Contact-356 ContactSensor DETECTED

Code: Select all

#!/usr/bin/env python3

import sys
import time
import datetime
import json
import uuid
import os
import requests
from uuid import uuid4

ALEXA_URI = "https://api.eu.amazonalexa.com/v3/events"
CODE = "RHwmsruJWOeBfqHdFEfN"
CLIENT_ID = "the.client.id.from.the.permissions.for.send.alexa.events" 
CLIENT_SECRET = "the.client.secret.from.the.permissions.for.send.alexa.events"
REDIRECT_URI =  "https://www.amazon.com/ap/oa/?redirect_url=https://layla.amazon.com/api/skill/link/xxxxxxxxxxxxxxxxxx"

PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300

PATH = os.path.dirname(os.path.abspath(__file__))
if ("scripts" in PATH):
    TOKEN_FILENAME = "/home/phil/domoticz/scripts/" + CODE + ".txt"
else:
    TOKEN_FILENAME = CODE + ".txt"
UTC_FORMAT = "%Y-%m-%dT%H:%M:%S.00Z"
LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token"
LWA_HEADERS = {
    "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"}


def get_utc_timestamp(seconds=None):
    return time.strftime(UTC_FORMAT, time.gmtime(seconds))


def get_utc_timestamp_from_string(string):
    return datetime.datetime.strptime(string, UTC_FORMAT)


def get_uuid():
    return str(uuid.uuid4())


def get_need_new_token():
    """Checks whether the access token is missing or needed to be refreshed"""
    need_new_token_response = {
        "need_new_token": False,
        "access_token": "",
        "refresh_token": ""
    }

    if os.path.isfile(TOKEN_FILENAME):
        with open(TOKEN_FILENAME, 'r') as infile:
            last_line = infile.readlines()[-1]
        token = last_line.split("***")
        parts = len(token)
        # sometimes we get extra or zero LFs in the last line so compensate
        if parts == 3:
            real_last_line = last_line.split("}")[1] + "}"
            token = real_last_line.split("***")
        token_received_datetime = get_utc_timestamp_from_string(token[0])
        token_json = json.loads(token[1])
        token_expires_in = token_json["expires_in"] - \
            PREEMPTIVE_REFRESH_TTL_IN_SECONDS
        token_expires_datetime = token_received_datetime + \
            datetime.timedelta(seconds=token_expires_in)
        current_datetime = datetime.datetime.utcnow()

        need_new_token_response["need_new_token"] = current_datetime > token_expires_datetime
        need_new_token_response["access_token"] = token_json["access_token"]
        need_new_token_response["refresh_token"] = token_json["refresh_token"]
    else:
        need_new_token_response["need_new_token"] = True

    return need_new_token_response


def get_access_token():
    """Performs access token or token refresh request as needed and returns valid access token"""

    need_new_token_response = get_need_new_token()
    access_token = ""

    if need_new_token_response["need_new_token"]:
        if os.path.isfile(TOKEN_FILENAME):
            with open(TOKEN_FILENAME, 'a') as outfile:
                outfile.write("\n")

            lwa_params = {
                "grant_type": "refresh_token",
                "refresh_token": need_new_token_response["refresh_token"],
                "client_id": CLIENT_ID,
                "client_secret": CLIENT_SECRET,
                "redirect_uri": REDIRECT_URI
            }
        else:
            lwa_params = {
                "grant_type": "authorization_code",
                "code": CODE,
                "client_id": CLIENT_ID,
                "client_secret": CLIENT_SECRET,
                "redirect_uri": REDIRECT_URI
            }

        response = requests.post(
            LWA_TOKEN_URI, headers=LWA_HEADERS, data=lwa_params, allow_redirects=True)

        if response.status_code != 200:
            return None

        token = get_utc_timestamp() + "***" + response.text
        with open(TOKEN_FILENAME, 'a') as outfile:
            outfile.write(token)

        access_token = json.loads(response.text)["access_token"]
    else:
        access_token = need_new_token_response["access_token"]

    return access_token


def main(argv):
    token = get_access_token()

    # The full endpointId such as Contact-356 (basically, type and idx separated by hyphen)
    endpointId = argv[0]
    # The type of the sensor to report, e.g. ContactSensor or MotionSensor
    devType = argv[1]
    # This is DETECTED if the sensor is active/open and NOT_DETECTED if it is inactive/closed
    devValue = argv[2]

    data = {'event': {'header': {'namespace': 'Alexa', 'name': 'ChangeReport', 'messageId': str(uuid4()),
                                 'payloadVersion': '3'}, 'payload': {'change': {'cause': {'type': 'PHYSICAL_INTERACTION'},
                                                                                'properties': [{'name': 'connectivity', 'namespace': 'Alexa.EndpointHealth', 'value': {'value': 'OK'},
                                                                                                'timeOfSample': datetime.datetime.now().replace(microsecond=0).isoformat()+"Z", 'uncertaintyInMilliseconds': 100},
                                                                                               {'name': 'detectionState', 'namespace': 'Alexa.'+devType, 'value': devValue,
                                                                                                   'timeOfSample': datetime.datetime.now().replace(microsecond=0).isoformat()+"Z", 'uncertaintyInMilliseconds': 100}]}},
                      'endpoint': {
        'scope': {'type': 'BearerToken', 'token': token}, 'endpointId': endpointId}}, 'context': {}}

    headers = {'Content-Type': 'application/json'}
    headers['Authorization'] = 'Bearer ' + token
    response = requests.post(ALEXA_URI, headers=headers, json=data)
    print(data, response)


if __name__ == "__main__":
    main(sys.argv[1:])

It's basically just a re-write of the main method in my earlier posting, with the imports changed.

Note it will only work in en-US and also only really be cool if you have the Alexa.EndpointHealth interface in all your endpoints. See next post.

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Tuesday 26 November 2019 21:32
by philchillbill
To make sure you are reporting EndpointHealth, the following changes need to be made to the Lambda files:

In AlexaSmartHome.py. add:

Code: Select all

@INTERFACES.register('Alexa.EndpointHealth')
class AlexaEndpointHealth(AlexaInterface):
    def name(self):
        return 'Alexa.EndpointHealth'

    def propertiesSupported(self):
        return [{'name': 'connectivity'}]
At the top of the method serializeProperties, under the 'for prop in self.propertiesSupported():', after 'prop_name = prop['name']', add

Code: Select all

            if prop_name == 'connectivity':
                prop_value = { 'value': 'OK' }
Finally, in DomoticzHandler.py, close to the start of the file and immediately under 'class DomoticzEndpoint(AlexaEndpoint):' add the following:

Code: Select all

    def __init__(self, endpointId, friendlyName='', description='', manufacturerName=''):
        super().__init__(endpointId, friendlyName, description, manufacturerName)
        self.addCapability(AlexaEndpointHealth(self, 'Alexa.EndpointHealth',[{'name': 'connectivity'}]))
        self._device_ = None
After doing that, do a device discovery to add this new capability to all your endpoints. Without it, change reports work in routines but you cannot ask if a door is open or closed. With it, you can ask "Alexa, is the front door open" and get a spoken response "Front door is closed". :mrgreen:

Alexicz - FREE Domoticz to Alexa Integration

Posted: Wednesday 27 November 2019 8:03
by philchillbill
I’m getting an error of "urllib.error.URLError: <urlopen error EOF occurred in violation of protocol (_ssl.c:852)>"
Is your Domoticz URL as entered in the config JSON file a https one?

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Wednesday 27 November 2019 20:34
by tmh88
philchillbill wrote: Tuesday 26 November 2019 21:32 To make sure you are reporting EndpointHealth, the following changes need to be made to the Lambda files:

In AlexaSmartHome.py. add:

Code: Select all

@INTERFACES.register('Alexa.EndpointHealth')
class AlexaEndpointHealth(AlexaInterface):
    def name(self):
        return 'Alexa.EndpointHealth'

    def propertiesSupported(self):
        return [{'name': 'connectivity'}]
At the top of the method serializeProperties, under the 'for prop in self.propertiesSupported():', after 'prop_name = prop['name']', add

Code: Select all

            if prop_name == 'connectivity':
                prop_value = { 'value': 'OK' }
Finally, in DomoticzHandler.py, close to the start of the file and immediately under 'class DomoticzEndpoint(AlexaEndpoint):' add the following:

Code: Select all

    def __init__(self, endpointId, friendlyName='', description='', manufacturerName=''):
        super().__init__(endpointId, friendlyName, description, manufacturerName)
        self.addCapability(AlexaEndpointHealth(self, 'Alexa.EndpointHealth',[{'name': 'connectivity'}]))
        self._device_ = None
After doing that, do a device discovery to add this new capability to all your endpoints. Without it, change reports work in routines but you cannot ask if a door is open or closed. With it, you can ask "Alexa, is the front door open" and get a spoken response "Front door is closed". :mrgreen:
Hi, with this will I be able to ask alexa if eg a light is turned on or off. I'm using philiphs hue. Ta

Re: Alexicz - FREE Domoticz to Alexa Integration

Posted: Wednesday 27 November 2019 20:37
by tmh88
philchillbill wrote: Wednesday 27 November 2019 8:03
I’m getting an error of "urllib.error.URLError: <urlopen error EOF occurred in violation of protocol (_ssl.c:852)>"
Is your Domoticz URL as entered in the config JSON fike a https one?
Thanks for the reply. Yes sorted it now, I had rebooted domoticz when adding a user and it hadn't fully restarted. Well at least I think that's what it was

Alexicz - FREE Domoticz to Alexa Integration

Posted: Wednesday 27 November 2019 22:50
by philchillbill

Hi, with this will I be able to ask alexa if eg a light is turned on or off. I'm using philiphs hue. Ta
Nope. Not yet at least. Only works for contact sensors. Amazon limitation.


Sent from my iPhone using Tapatalk