Iaqualink
Go package for talking with iAquaLink pool robots, including Polaris robots.
Install / Use
/learn @tekkamanendless/IaqualinkREADME
iaqualink
Go package for talking with iAquaLink/Zodiac pool robots.
This package has been built by reverse-engineering the iAquaLink HTTP protocol using mitmproxy.
This package is very much in development, and there's a good chance that your specific pool robot is not properly supported.
If you'd like to help out, set up an mitmproxy and send me your traffic.
Tested With
- Polaris P965IQ (device type
i2d_robot) - Zodiac Voyager RE 4400 iQ (device type
cyclonext)
Device Types
cyclonext; this definitely includes teh Zodiac Voyager.exo; this appears to be a chlorinator of some kind.id2_robot; this definitely includes the Polaris IQ models.
Zodiac API
Base: https://prod.zodiac-io.com
POST /devices/v1/${device}/ota
Returns information on if the device has an update available.
Headers:
Authorization: the ID token.
JSON request:
deviceType; the device type.forceOTA; eithertrueorfalse(I've only ever seen it usetrue).
JSON response:
code; some kind of numeric code.message; a human-readable message.
The only values that I'm aware of so far are:
6:Device is already on latest firmware
GET /devices/v2/${device}/shadow
This seems to be identical to GET /devices/v1/${device}/shadow.
POST /devices/v1/${device}/shadow
This appears to let you update the state of the device.
Headers:
Authorization: the ID token.
JSON request:
statedesiredequipment${key}; this is the key returned by theGETversion of the endpoint.- (Whatever field(s?) you want to change.
For example, for an exo chlorinator, you might set production: 1 or production: 0 to turn it on or off, respectively.
JSON response:
This is practically identical to the response from GET /devices/v2/${device}/shadow.
GET /devices/v2/${device}/features
This returns a list of features that the device supports.
Headers:
Authorization: the ID token.
GET /devices/v2/${device}/info
TODO: ???
Headers:
Authorization: the ID token.
GET /devices/v2/${device}/site
TODO: ???
Headers:
Authorization: the ID token.
GET /devices/v2/${device}/shadow
This appears to return the current state of the device.
Headers:
Authorization: the ID token.
JSON response:
deviceId; the device identifier.state; the state.reported; the state information as reported by the device.aws; probably AWS-related debug information?debug; debug information?equipment; an object where each key represents a thing of some kind that can be manipulated.${key}; a thing of some kind. The properties vary based on the device type.
- (There are other fields)
POST /push/v1/users/unregister-mobile
TODO: ???
POST /users/v1/login
This logs you into the Zodiac API and provides you with a user ID and authentication token. These will be used by the iAquaLink API.
JSON body:
apiKey:EOOEMOW4YR6QNB07; this appears to be constant.email; your e-mail address.password; your password.
JSON response (relevant subset):
authentication_token; this is the authentication token.email; the same e-mail address.id; this is the user ID.session_id; ???userPoolOAuth; this contains information about OAuth, which is used for newer robot models.AccessToken; the access token.ExpiresIn; when the (access?) token expires.IdToken; the ID token.RefreshToken; the refresh token; this can be used to get a newAccessTokenvalue.TokenType; this seems to always beBearer, even though it's not technically used correctly.
POST /users/v1/refresh
This uses the user's e-mail addresss and RefreshToken to effectively log in again.
JSON body:
email; the user's e-mail address.refresh_token; the user'srefreshTokenfromPOST /users/v1/login.
JSON response:
The output is similar to that of POST /users/v1/login.
Note, however, that userPoolOAuth.RefreshToken will not be present.
iAquaLink PRM(?) API
Base: https://prm.iaqualink.net
POST /v2/signout
TODO: ???
Headers:
Authorization: the authorization token.
iAquaLink API
Base: https://r-api.iaqualink.net
GET /devices.json
This lists the devices on your account. You'll need the device ID for any device-related endpoints.
Query parameters:
api_key:EOOEMOW4YR6QNB07; this appears to be constantauthentication_token; your authentication tokenuser_id; your user ID
Response JSON:
- An array of objects, each representing a device.
device_type; the device type.name; the name given by the user.serial_number; the identifier that will be used to subsequently identify the device.
(There are other fields, but they aren't particularly helpful.)
POST /devices/${device}/execute_read_command.json
This executes a command on a device.
Query parameters:
api_key:EOOEMOW4YR6QNB07; this appears to be constantauthentication_token; your authentication tokenuser_id; your user IDcommand; the commandparams; a query string for the parameters for the command
Commands:
/command- Parameters:
request; the request codetimeout; the timeout (iAquaLink default:800)
- Parameters:
Request codes:
OAOD; list the scheduleOA11; list the status0A1210; stop / clear error0A1240; start0A1300; subtract 30 minutes0A1301; add 30 minutes0A1700; lift system, stop0A1701; lift system, lift0A1B42; backward0A1B46; forward0A1B4C; turn left0A1B52; turn right
Request code format:
0A<command>[<subcommand>]
Commands:
-
0D; list the schedule -
11; list the status -
12; start/stop10; stop / clear error40; start
-
13; add/subtract time00; subtract 30 minutes01; add 30 minutes
-
17; lift system00; stop01; start
-
1B; remote control42; ASCII "B" - backward46; ASCII "F" - forward4C; ASCII "L" - left52; ASCII "R" - right
Web Socket API
Base: https://prod-socket.zodiac-io.com
/devices
This endpoint appears to be the main endpoint for communication (when supported).
Note that in responses, the metadata object appears to be a mirror of state, but with timestamps where every bottom-level property is.
Action: Subscribe
SEND:
{
"action": "subscribe",
"version": 1,
"namespace": "authorization",
"payload": {
"userId": ${userId}
},
"service": "Authorization",
"target": "${device}"
}
RECEIVE:
{
"service": "Authorization",
"target": "${device}",
"namespace": "authorization",
"payload": {
"robot": {
"state": {
"reported": {
"aws": {
"status": "connected",
"timestamp": 1663228298244,
"session_id": "11ae4f85-2f9b-4b82-b726-3528c876ea0e"
},
"sn": "${device}",
"dt": "cyc",
"vr": "V21C10",
"payloadVer": 1,
"eboxData": {
"controlBoxSn": "${some-serial-number}",
"controlBoxPn": "${some-part-number}",
"completeCleanerSn": "${some-serial-number}",
"completeCleanerPn": "${some-part-number}",
"powerSupplySn": "${some-serial-number}",
"motorBlockSn": "${some-serial-number}"
},
"equipment": {
"robot.1": {
"mode": 0,
"direction": 0,
"cycle": 3,
"cycleStartTime": 1663140417,
"canister": 0,
"logger": 0,
"firstSmrtFlag": 1,
"stepper": 0,
"stepperAdjTime": 15,
"durations": {
"waterTim": 45,
"quickTim": 90,
"smartTim": 0,
"deepTim": 150,
"customTim": 75,
"firstSmartTim": 150,
"scanTim": 30
},
"customCyc": {
"type": 0,
"intensity": 0
},
"errors": {
"timestamp": 1663097347,
"code":0
},
"vr": "V21E11",
"equipmentId": "ND21015155",
"totRunTime": 13410
}
}
}
},
"metadata": {
"reported": {
"aws": {
"status": { "timestamp": 1663228298 },
"timestamp": { "timestamp": 1663228298 },
"session_id": { "timestamp": 1663228298 }
},
"sn": { "timestamp": 1662828488 },
"dt": { "timestamp": 1662828488 },
"vr": { "timestamp": 1662828488 },
"paylo
