Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
Hello,
Please, can anyone help with connecting a Ondilo spa monitor.
Authentication is to difficult for me
Here the api info how to auth:
https://interop.ondilo.com/docs/api/cust...entication
And here's the general api info:
https://interop.ondilo.com/docs/api/customer/v1/
I have a app username and password but i see nothing in the doc how to get access to the api with that user&pwd
Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
21.10.2020, 17:53
(This post was last modified: 21.10.2020, 18:29 by gjniewenhuijse .)
Ok, i give it a try
my code:
Code:
local usr = "my@email.com"
local pwd = "mypwd"
local uri = "https%3A%2F%2Fyour.app.url%2Fauthorize" -- there's no url redirect, so how to handle this
local state = "c7d37ee542c7'" -- random
---------------------------- change nothing after this line -------------------------------
local apiHost = "https://interop.ondilo.com"
local apiUrl = apiHost .. "/api/customer/v1"
local endpointToken = "/oauth2/token"
local endpointAuth = "/oauth2/authorize"
-- init
if not init then
require('socket.http')
require('ltn12')
require('json')
socket.http.TIMEOUT = 5
init = true
end
function getAuth()
url = apiHost..endpointAuth..'?client_id=customer_api&response_type=code&redirect_uri='..uri..'&scope=api&state='..state
method = "POST"
local response_body = { }
local payload = '{"login":"'..usr..'","password":"'..pwd..'","locale":"nl","proceed":"Authorize"}'
local res, code, response_headers, status = socket.http.request
{
url = url,
method = method,
headers =
{
["Accept"] = "application/json",
["Accept-Charset"] = "utf-8",
["Accept-Encoding"] = "gzip-deflate",
["Content-Type"] = "application/json",
["Content-Length"] = payload:len()
},
source = ltn12.source.string(payload),
sink = ltn12.sink.table(response_body)
}
log(res, code, response_headers, status)
log(response_body)
end
getAuth()
My return:
Code:
* arg: 1
* number: 1
* arg: 2
* number: 200
* arg: 3
* table:
["server"]
* string: Apache/2.4.29 (Ubuntu)
["content-type"]
* string: text/html;charset=UTF-8
["connection"]
* string: close
["access-control-allow-methods"]
* string: GET, POST, PUT, PATCH, DELETE, OPTIONS
["access-control-allow-headers"]
* string: X-Requested-With, Content-Type, Accept, Origin, Authorization
["cache-control"]
* string: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
["pragma"]
* string: no-cache
["content-length"]
* string: 4047
["date"]
* string: Wed, 21 Oct 2020 17:45:44 GMT
["vary"]
* string: Accept-Encoding
["access-control-allow-origin"]
* string:
["access-control-allow-credentials"]
* string: true
* arg: 4
* string: HTTP/1.1 200 OK
but how to get the code that is normally send to the url given in the var uri (see above) or is my code wrong?
documentation is here:
https://interop.ondilo.com/docs/api/cust...entication
if i do the same in a browser, my return url looks like:
https://your.app.url/authorize?code=6440...a88c&state =
c7 d3 7 ee54 2c 7
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
What do you get in response body? You should also remove Accept-Encoding header otherwise body will be compressed with gzip instead of plain text.
Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
(22.10.2020, 06:30) admin Wrote: What do you get in response body? You should also remove Accept-Encoding header otherwise body will be compressed with gzip instead of plain text.
I see the plain website, i think the form is not filled and executed. But how to do this.
same source as:
https://interop.ondilo.com/oauth2/author...d44ef532c5
Code:
* table:
[1]
* string:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Ondilo authorization page</title>
<style>
* {
margin: 10px 4px 10px 4px;
}
.box {
width: 340px;
margin: auto;
border: 1px solid rgb(41, 165, 155);
}
.banner {
margin: 0px;
background-color: rgb(41, 165, 155);
height: 50px;
position: relative;
}
.ico {
position: absolute;
top: 3px;
left: 4px;
}
.ondilo {
position: absolute;
top: -1px;
right: 4px;
}
strong {
margin: 0px;
}
label {
margin-top: 8px;
margin-bottom: 0px;
display: block;
font-size: 0.8em;
color: gray;
}
input, select {
font-size: 1.2em;
}
input[type=text], input[type=password], select {
width: 312px;
}
input[type=submit] {
width: 322px;
display: block;
margin-bottom: 25px;
}
.authorize {
color: white;
background-color: rgb(41, 165, 155);
margin-bottom: 10px;
margin-top: 25px;
border-color: rgb(41, 165, 155);
}
p {
text-align: center;
margin-top: 25px;
margin-bottom: 25px;
}
.warning p, .warning ul {
margin: 0 0 0 0;
line-height: 15px;
}
</style>
</head>
<body>
<div class="box">
<div class="banner">
<img src="/images/ico.png" alt="ICO" title="ICO" class="ico"/
[2]
* string: >
<img src="/images/ondilo.png" alt="Ondilo" title="Ondilo" class="ondilo"/>
</div>
<form method="post">
<p>Sign in to allow <strong>customer_api</strong> to connect to your <strong>Ondilo</strong> account and access the related data</p>
<label for="login">Your email address</label><input type="text" name="login" ><br/>
<label for="pasword">Your password</label><input type="password" name="password">
<label for="locale">Your language</label><select id="locale" name="locale">
<option value="en">English</option>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="de">Deutsch</option>
<option value="pt">Português</option>
<option value="nl">Nederlands</option>
<option value="it">Italiano</option>
<option value="cs">Čeština</option>
<option value="hu">Magyar</option>
<option value="sv">Svenska</option>
<option value="ro">Română</option>
</select>
<div class="warning">
<p>
We remind that ICO takes measures every hour.</br>
In order to avoid excessive load of our servers, the requests to the Ondilo Customer API are limited to the following per user quotas :
<ul>
<li>5 requests per second</li>
<li>30 requests per hour</li>
</ul>
</p>
</div>
<input type="submit" name="proceed" value="Authorize" class="authorize">
<input type="submit" name="proceed" value="Cancel">
</form>
</div>
</body>
</html>
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
For auth you need to use standard POST, not JSON:
Code:
function encodepost(t)
local res = {}
local esc = require('socket.url').escape
for k, v in pairs(t) do
res[ #res + 1 ] = esc(k) .. '=' .. esc(v)
end
return table.concat(res, '&')
end
payload = encodepost({
login = usr,
password = pwd,
locale = "nl",
proceed = "Authorize",
})
For this request Accept/Content-Type headers are not needed. Add
redirect = false to the request table to catch the redirect URL instead of following it.
Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
(22.10.2020, 07:14) admin Wrote: For auth you need to use standard POST, not JSON:
For this request Accept/Content-Type headers are not needed. Add redirect = false to the request table to catch the redirect URL instead of following it.
mmm same result with changed code
Code:
function getAuth()
function encodepost(t)
local res = {}
local esc = require('socket.url').escape
for k, v in pairs(t) do
res[ #res + 1 ] = esc(k) .. '=' .. esc(v)
end
return table.concat(res, '&')
end
url = apiHost..endpointAuth..'?client_id=customer_api&response_type=code&redirect_uri='..uri..'&scope=api&state='..state
method = "POST"
local response_body = {}
local payload = encodepost({
login = usr,
password = pwd,
locale = "nl",
proceed = "Authorize",
})
local res, code, response_headers, status = socket.http.request
{
url = url,
method = method,
redirect = false,
headers =
{
["Content-Length"] = #payload
},
source = ltn12.source.string(payload),
sink = ltn12.sink.table(response_body)
}
if res and code == 200 then
log(response_body)
log(res, code, response_headers, status)
else
log(res, code, response_headers, status)
end
end
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
Header is missing:
Code:
["Content-Type"] = "application/x-www-form-urlencoded"
Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
(22.10.2020, 08:23) admin Wrote: Header is missing:
Code:
["Content-Type"] = "application/x-www-form-urlencoded"
yes, now i get the authorization code (authCode).
Next is exchaging the authorization code for an access token, see my code try below.
Doc:
https://interop.ondilo.com/docs/api/cust...n-exchange
Code:
-- exchange the authorization code for an access token
if authCode then
url = apiHost..endpointToken..'?code='..authCode..'&grant_type=authorization_code&client_id=customer_api&redirect_uri='..uri
method = "POST"
local response_body = {}
local res, code, response_headers, status = socket.http.request
{
url = url,
method = method,
headers =
{
["Content-Type"] = "application/x-www-form-urlencoded"
},
source = ltn12.source.string(''),
sink = ltn12.sink.table(response_body)
}
log(res, code, response_headers, status)
end
but as usually i received an error
Code:
* arg: 1
* number: 1
* arg: 2
* number: 400
* arg: 3
* table:
["server"]
* string: Apache/2.4.29 (Ubuntu)
["content-type"]
* string: application/json
["connection"]
* string: close
["access-control-allow-methods"]
* string: GET, POST, PUT, PATCH, DELETE, OPTIONS
["access-control-allow-headers"]
* string: X-Requested-With, Content-Type, Accept, Origin, Authorization
["cache-control"]
* string: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
["pragma"]
* string: no-cache
["date"]
* string: Thu, 22 Oct 2020 11:14:32 GMT
["content-length"]
* string: 97
["access-control-allow-origin"]
* string: *
["access-control-allow-credentials"]
* string: true
* arg: 4
* string: HTTP/1.1 400 Bad Request
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
You must provide "Content-Length" header for POST requests. Move your GET variables to POST payload:
Code:
url = apiHost..endpointToken
payload = encodepost({
code = authCode,
grant_type = 'authorization_code',
client_id = 'customer_api',
redirect_uri = uri,
})
Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
22.10.2020, 16:43
(This post was last modified: 23.10.2020, 06:39 by gjniewenhuijse .)
many thanks to admin for the support.
here my first version to get the latest sensor readings for one pool/spa.
We remind that ICO takes measures every hour.
In order to avoid excessive load of our servers, the requests to the Ondilo Customer API
are limited to the following per user quotas :
5 requests per second
30 requests per hour
Access tokens have a lifetime of one hour, while refresh tokens are non-expiring.
Attached Files
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
One small improvement, you should use the whole table instead of only the first element. If the JSON response is large enough then the response table will have more than 1 element. Use this instead:
Code:
data = json.decode(table.concat(response_body))
Posts: 6
Threads: 2
Joined: Oct 2018
Reputation:
0
Hello
Could you give some instructions with examples how to get sensors data please
I am not familiar with scripting, but can multiply solution from example.
Thank you
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
First you need to set usr and pwd variables in the script. Then run this function to get the ID of your pool:
Code:
log( getPoolInfo() )
Then you can request the sensor state by calling getSensorInfo:
Code:
data = getSensorInfo()
log(data)
When the data format is known the script can be modified to write sensor state to objects.
Posts: 6
Threads: 2
Joined: Oct 2018
Reputation:
0
(24.05.2021, 09:38) admin Wrote: First you need to set usr and pwd variables in the script. Then run this function to get the ID of your pool:
Code:
log( getPoolInfo() )
Then you can request the sensor state by calling getSensorInfo:
Code:
data = getSensorInfo()
log(data)
When the data format is known the script can be modified to write sensor state to objects.
Hello
I have got sensors data, thank you, could you make example of a function for the PUT method?
Thank you
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
PUT is the same as POST, just change the method field accordingly
Posts: 6
Threads: 3
Joined: Dec 2020
Reputation:
0
12.06.2025, 10:51
(This post was last modified: 12.06.2025, 11:15 by function .)
I know that this question is quite some time later than the last post, but is this code still usable or is the API changed? I got respons 419 when getAuth() is triggered.
Respons_body:
Code:
* table:
[1]
* string: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Expired</title>
<style>
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}code{font-family:monospace,monospace;font-size:1em}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}code{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-400{--border-opacity:1;border-color:#cbd5e0;border-color:rgba(203,213,224,var(--border-opacity))}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xl{max-width:36rem}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tracking-wider{letter-spacing:.05em}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}}
</style>
<style>
body {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
</style>
</head>
<body class="antialiased">
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
<div class="max-w-xl mx-auto sm:px-6 lg:px-8">
<div class="flex items-center pt-8 sm:justify-start sm:pt-0">
<div class="px-4 text-lg text-gray-500 border-r border-gray-400 tracking-wider">
419 </div>
<div class="ml-4 text-lg text-gray-500 uppercase tracking-wider">
Page Expired </div>
</div>
</div>
</div>
</body>
</html>
Posts: 458
Threads: 97
Joined: Jun 2015
Reputation:
6
Posts: 8189
Threads: 43
Joined: Jun 2015
Reputation:
473
Try this as a starting point to get the token:
Code:
function getAuth()
local url = apiHost..endpointAuth..'?client_id=customer_api&response_type=code&redirect_uri='..uri..'&scope=api&state='..state
local resp, code, hdrs, stat = socket.http.request(url = url)
local formtoken = resp:match('name="_token" value="([^"]+)">')
local cookies = hdrs['set-cookie'] or ''
local cookietoken = cookies:match('XSRF%-TOKEN=[^;]+') or ''
local cookiesession = cookies:match('interop_new_api_session=[^;]+') or ''
local escape = require('socket.url').escape
local data = {
'_token=' .. formtoken,
'email=' .. escape(usr),
'password=' .. escape(pwd),
'locale=en',
'proceed=Authorize'
}
local body = table.concat(data, '&')
local resp, code, hdrs, stat = socket.http.request({
url = url,
method = 'POST',
headers = {
['Content-Type'] = 'application/x-www-form-urlencoded',
['Content-Length'] = #body,
['Referer'] = url,
['Cookie'] = cookietoken .. '; ' .. cookiesession,
},
body = body
})
log(resp)
log(code)
log(hdrs)
end