Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/workaround for #204 #124

Merged
merged 20 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ For use in Home Assistant: https://github.com/jm-73/Indego

## Basic information needed

The library requires python 3.7 or above.
The library requires python 3.8 or above.

Required information | Description
-----------------------|------------
Expand Down Expand Up @@ -100,7 +100,7 @@ Calendar(cal=3, days=[CalendarDay(day=0, day_name='monday', slots=[CalendarSlot(
```

### indego.update_config()
Updates indego.config with settings for region, border cut, pin lock, id for the wire, bump and alarm mode. This call doesnt work on some Indegos, this function gives an error on Indego 1000, while it works on newer models (e.g., Indego S+ 400).
Updates indego.config with settings for region, border cut, pin lock, id for the wire, bump and alarm mode. This call doesn't work on some Indegos, this function gives an error on Indego 1000, while it works on newer models (e.g., Indego S+ 400).

```python
Config(region=0, language=1, border_cut=0, is_pin_set=True, wire_id=4, bump_sensitivity=0, alarm_mode=True)
Expand Down Expand Up @@ -174,7 +174,7 @@ Security(enabled=True, autolock=False)
```

### indego.update_setup()
Updates the indego.setup with information if the Indego is set up, has pincode and wome other unknown values.
Updates the indego.setup with information if the Indego is set up, has pincode and some other unknown values.

```python
Setup(hasOwner=True, hasPin=True, hasMap=True, hasAutoCal=False, hasIntegrityCheckPassed=True)
Expand All @@ -197,7 +197,7 @@ State(state=64513, map_update_available=True, mowed=78, mowmode=0, xPos=162, yPo
### indego.update_state(force=False, longpoll=True, longpoll_timeout=120)
Updates the indego.state with state of mower, % lawn mowed, position, runtime, map coordinates.

When longpoll is set to True, the indego.state must contain a value. It should contain the current state of the mower (you must run a "regular" update.state first). You send the current value to the API, and the API answers back when the state chenges.
When longpoll is set to True, the indego.state must contain a value. It should contain the current state of the mower (you must run a "regular" update.state first). You send the current value to the API, and the API answers back when the state changes.

This function can be used instead of polling the status every couple of seconds: place one longpoll status request with a timeout of max. 300 seconds and the function will provide its return value when the status has been updated. As soon as an answer is received, the next longpoll status request can be placed. This should save traffic on both ends.

Expand Down Expand Up @@ -237,19 +237,19 @@ Set all alerts to read, the function loops through the alert_id's from indego.al
### indego.put_command(command)
Send commands.

Command |Description
Command |Description
------------|--------------------
put_command('mow') |Start mowing
put_command('pause') |Pause mower
put_command('mow') |Start mowing
put_command('pause') |Pause mower
put_command('returnToDock') |Return mower to dock

### indego.put_mow_mode(command)
Send command. Accepted commands:

Command |Description
Command |Description
------------|--------------------
put_mow_mode('true') |Smart Mow enabled
put_mow_mode('false') |Smart Mow disabled
put_mow_mode('true') |Smart Mow enabled
put_mow_mode('false') |Smart Mow disabled


## Not implemented yet
Expand Down Expand Up @@ -311,7 +311,7 @@ Response:


## Attributes for reading data from locally cached API data
All functions that doesnt contain "update" first in name is collecting data from locally stored variables in the function. No API calls to Bosch or mower.
All functions that doesn't contain "update" first in name is collecting data from locally stored variables in the function. No API calls to Bosch or mower.

attributes | Description
-------------------------|-----------------------------
Expand All @@ -324,7 +324,7 @@ AlmName | Show name.
BareToolNumber | Show the model number.
Battery | Show battery information.
BatteryAmbientTemp | Show the ambient temp of the battery.
BatteryCycles | Dont know what this value is?
BatteryCycles | Don't know what this value is?
BatteryDischarge | Show the current drawn in Ah.
BatteryPercent | Show the raw value for percentage left. For Gen 1 this seems to be the battery voltage. For Gen 2 it seems to be the actual percentage left in the battery.
BatteryPercentAdjusted | Show the adjusted value for percentage left. Calculated for Gen 1, and the actual percentage value for Gen 2.
Expand All @@ -333,13 +333,13 @@ BatteryVoltage | Show voltage for the battery. For Gen 1 mowers this value seems
ConvertBoschDateTime | Convert Bosch abbreviation for time to std 24h time
Country | Show country for the account.
Displayname | Show name for the account.
Email | Show email adress for the account.
Email | Show email address for the account.
FirmwareAvailable | Show if there are any firmware updates available.
FriendlyAlertErrorCode | Show user friendly alert error code description to be shown in HA GUI.
Garden | Dont know what this is?
HmiKeysn | Dont know what this is?
Garden | Don't know what this is?
HmiKeysn | Don't know what this is?
Language | Show language for the account.
MapSvgCacheTs | Dont know what this is...
MapSvgCacheTs | Don't know what this is...
MapUpdateAvailable | Show if there is an update of the map image.
ModelDescription | Show user friendly model name.
ModelVoltage | Show the predefined voltage limits in order to calculate battery percentage.
Expand All @@ -351,11 +351,11 @@ MowerState | Show current state
MowerStateDescription | Show simple description of current state. States available are Docked, Mowing, Stuck, Diagnostics mode, End of life, Software update.
MowerStateDescriptionDetailed | Show description in detail of current state.
MowingModeDescription | Show the user friendly mow mode description.
NeedsService | Show needs service flag. Dont know when it is used.
NeedsService | Show needs service flag. Don't know when it is used.
NextMow | Show next planned mow session.
OptIn | Dont know what this are for?
OptInApp | Dont know what this are for?
Runtime | Show session and total rutime and charge time in minutes.
OptIn | Don't know what this are for?
OptInApp | Don't know what this are for?
Runtime | Show session and total runtime and charge time in minutes.
RuntimeSession | show session runtime and charge time in minutes
RuntimeTotal | Show total runtime and charge time in hours
Serial | Show serial number
Expand All @@ -369,7 +369,7 @@ YPos | Show y-position of mower.

### Not properly implemented yet

update_predicitive_calendar()
update_predictive_calendar()
Get the calender for predicted mow sessions

update_user_adjustment()
Expand Down Expand Up @@ -419,3 +419,23 @@ put
delete
/alerts/<alertid>
```
# Contributing
The project development is done in a poetry virtual environment.

## setup your environment
To start development please install [poetry](https://python-poetry.org/docs/).

* Install all dependency by running `poetry install`.
* activate the virtual environment by running `poetry shell`
* Run `python test_new.py` to test your setup.

## setup your personal info

Open [config.json](./config.json) and type in the information for your `indego`.

```javascript
{
"token": "mytoken",
"serial": "myserial"
}
```
5 changes: 2 additions & 3 deletions examples/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"username": "xxx@xxx.xx",
"password": "xxx",
"serial": "xxxxxxxxx"
"token": "<put your token here>",
"serial": "123456789"
}
54 changes: 32 additions & 22 deletions examples/print_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
# -*- coding: utf-8 -*-

import json
import logging
from pyIndego import IndegoClient

logging.basicConfig(filename="pyIndego.log", level=logging.DEBUG)
_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel(logging.DEBUG)

def country_operator(mcc, mnc):
"""get the country and network operator from network ID's"""
if mcc == 262:
country = "Germany"
if mnc == 1:
Expand Down Expand Up @@ -40,30 +46,34 @@ def country_operator(mcc, mnc):


def main(config):
"""example of how to instantiate a indego object and get the network info"""
with IndegoClient(**config) as indego:
indego.update_network()

(country, operator) = country_operator(indego.network.mcc, indego.network.mnc)
if country is not None:
print("Country is:", country)
if operator is not None:
print("Operator is:", operator)

if indego.network is not None:
(country, operator) = country_operator(indego.network.mcc, indego.network.mnc)
if country is not None:
print("Country is:", country)
if operator is not None:
print("Operator is:", operator)
else:
print("Operator is unknown")
else:
print("Operator is unknown")
print("Country and operator are unknown")

print("Signal strength (rssi):", indego.network.rssi)

print("Available Networks:")
for i in range(indego.network.networkCount):
(country, operator) = country_operator(int(str(indego.network.networks[i])[:3]), int(str(indego.network.networks[i])[3:5]))
if (country is not None) and (operator is not None):
print("\t", country, ":", operator)
else:
print("\tmcc =", str(indego.network.networks[i])[:3], ": mnc =", str(indego.network.networks[i])[3:5])
else:
print("Country and operator are unknown")

print("Signal strength (rssi):", indego.network.rssi)

print("Available Networks:")
for i in range(indego.network.networkCount):
(country, operator) = country_operator(int(str(indego.network.networks[i])[:3]), int(str(indego.network.networks[i])[3:5]))
if (country is not None) and (operator is not None):
print("\t", country, ":", operator)
else:
print("\tmcc =", str(indego.network.networks[i])[:3], ": mnc =", str(indego.network.networks[i])[3:5])

print("Error getting network info")

if __name__ == "__main__":
with open("config.json", "r") as config_file:
config = json.load(config_file)
main(config)
with open("config.json", "r",encoding="utf-8") as config_file:
_config = json.load(config_file)
main(_config)
25 changes: 17 additions & 8 deletions examples/print_next_mowing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@
# -*- coding: utf-8 -*-

import json
import logging
from datetime import datetime, timezone, timedelta
from pyIndego import IndegoClient

logging.basicConfig(filename="pyIndego.log", level=logging.DEBUG)
_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel(logging.DEBUG)

def main(config):
"""example of how to instantiate a indego object and get the mowing time"""
with IndegoClient(**config) as indego:

indego.update_next_mow()
print("Next mowing:", indego.next_mow)

nowDate = datetime.now(timezone.utc)
if (indego.next_mow - nowDate) < timedelta(hours=2, minutes=30):
print("Less than two and a half hours before mowing.")

now_date = datetime.now(timezone.utc)
if indego.next_mow is not None:
if (indego.next_mow - now_date) < timedelta(hours=2, minutes=30):
print("Less than two and a half hours before mowing.")
else:
print("Error getting mowing time")

if __name__ == "__main__":
with open("config.json", "r") as config_file:
config = json.load(config_file)
main(config)
with open("config.json", "r", encoding="utf-8") as config_file:
_config = json.load(config_file)
main(_config)
49 changes: 29 additions & 20 deletions examples/print_predictive_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,41 @@
# -*- coding: utf-8 -*-

from datetime import datetime
import logging
import json
from pyIndego import IndegoClient

logging.basicConfig(filename="pyIndego.log", level=logging.DEBUG)
_LOGGER = logging.getLogger(__name__)
_LOGGER.setLevel(logging.DEBUG)

def main(config):
"""example of how to instantiate a indego object and get the schedule information"""
with IndegoClient(**config) as indego:
indego.update_predictive_schedule()

print("Times where SmartMowing is planing to mow the lawn:")

for i in range(datetime.now().weekday(), datetime.now().weekday()+7):
for j in range(len(indego.predictive_schedule.schedule_days)):
if (indego.predictive_schedule.schedule_days[j].day == (i % 7)):
print("\t{}".format(indego.predictive_schedule.schedule_days[j].day_name))
for k in range(len(indego.predictive_schedule.schedule_days[j].slots)):
print('\t\t{:%H:%M} - {:%H:%M}'.format(indego.predictive_schedule.schedule_days[j].slots[k].start, indego.predictive_schedule.schedule_days[j].slots[k].end))

print("Times that are excluded for mowing from SmartMowing:")

for i in range(datetime.now().weekday(), datetime.now().weekday()+7):
for j in range(len(indego.predictive_schedule.exclusion_days)):
if (indego.predictive_schedule.exclusion_days[j].day == (i % 7)):
print("\t{}".format(indego.predictive_schedule.exclusion_days[j].day_name))
for k in range(len(indego.predictive_schedule.exclusion_days[j].slots)):
print('\t\t{:%H:%M} - {:%H:%M} {}'.format(indego.predictive_schedule.exclusion_days[j].slots[k].start, indego.predictive_schedule.exclusion_days[j].slots[k].end, indego.predictive_schedule.exclusion_days[j].slots[k].Attr))

if indego.predictive_schedule is not None:
for i in range(datetime.now().weekday(), datetime.now().weekday()+7):
for j in range(len(indego.predictive_schedule.schedule_days)):
if (indego.predictive_schedule.schedule_days[j].day == (i % 7)):
print("\t{}".format(indego.predictive_schedule.schedule_days[j].day_name))
for k in range(len(indego.predictive_schedule.schedule_days[j].slots)):
print('\t\t{:%H:%M} - {:%H:%M}'.format(indego.predictive_schedule.schedule_days[j].slots[k].start, indego.predictive_schedule.schedule_days[j].slots[k].end))

print("Times that are excluded for mowing from SmartMowing:")

for i in range(datetime.now().weekday(), datetime.now().weekday()+7):
for j in range(len(indego.predictive_schedule.exclusion_days)):
if (indego.predictive_schedule.exclusion_days[j].day == (i % 7)):
print("\t{}".format(indego.predictive_schedule.exclusion_days[j].day_name))
for k in range(len(indego.predictive_schedule.exclusion_days[j].slots)):
print('\t\t{:%H:%M} - {:%H:%M} {}'.format(indego.predictive_schedule.exclusion_days[j].slots[k].start, indego.predictive_schedule.exclusion_days[j].slots[k].end, indego.predictive_schedule.exclusion_days[j].slots[k].Attr))
else:
print("Error getting predictive schedule info")

if __name__ == "__main__":
with open("config.json", "r") as config_file:
config = json.load(config_file)
main(config)
with open("config.json", "r",encoding="utf-8") as config_file:
_config = json.load(config_file)
main(_config)
16 changes: 8 additions & 8 deletions pyIndego/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Constants for pyIndego."""
from enum import Enum
import random
import string
from pyIndego.version import __version__


class Methods(Enum):
"""Enum with HTTP methods."""
Expand All @@ -20,12 +20,14 @@ class Methods(Enum):
CONTENT_TYPE = "Content-Type"
COMMANDS = ("mow", "pause", "returnToDock")

DEFAULT_HEADER = {
DEFAULT_HEADERS = {
CONTENT_TYPE: CONTENT_TYPE_JSON,
# We need to change the user-agent!
# The Microsoft Azure proxy seems to block all requests (HTTP 403) for the default 'python-requests' user-agent.
# We also need to use a random agent for each client: https://github.com/jm-73/pyIndego/issues/119
"User-Agent": ''.join(random.choices(string.ascii_uppercase + string.digits, k=12))
# The Microsoft Azure proxy WAF seems to block all requests (HTTP 403) for the default 'python-requests' user-agent.
# See issues:
# - https://github.com/jm-73/pyIndego/issues/119
# - https://github.com/jm-73/Indego/issues/204
'User-Agent': "HomeAssistant/Indego (%s)" % __version__
}
DEFAULT_LOOKUP_VALUE = "Not in database."

Expand Down Expand Up @@ -132,7 +134,6 @@ class Methods(Enum):
99999: "Offline",
}


MOWER_STATE_DESCRIPTION = {
0: "Docked",
101: "Docked",
Expand Down Expand Up @@ -216,7 +217,6 @@ class Methods(Enum):
"firmware.updateComplete": "Software update complete",
}


DAY_MAPPING = {
0: "monday",
1: "tuesday",
Expand Down
Loading
Loading