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

feat: Jim Mobile support #185

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
71 changes: 38 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@
alt="Mobile Vikings"
align="right"
style="width: 200px;margin-right: 10px;" />
<img src="https://github.com/geertmeersman/mobile_vikings/raw/main/images/brand/jimmobile.png"
alt="Jim Mobile"
align="right"
style="width: 200px;margin-right: 10px;" />

# Mobile Vikings for Home Assistant
# Mobile Vikings & JimMobile mobile phone subscription information for Home Assistant

A Home Assistant integration to monitor Mobile Vikings BE services
A Home Assistant integration to monitor Mobile Vikings or Jim Mobile BE mobile phone subscription services

## Features

- View **Customer Info** such as name and account details.
- Track **Loyalty Points**: Available, blocked, and pending points.
- Monitor **Invoices**: Paid invoices, unpaid invoices, and the next expiration date.
- Track **Loyalty Points**: Available, blocked, and pending points. (only for Mobile Vikings)
- Monitor **Invoices**: Paid invoices, unpaid invoices, and the next expiration date. (only for Mobile Vikings)
- Access subscription details, including:
- **Data Usage**: Remaining data and percentage used.
- **Data Usage**: Remaining data and percentage used. (only for Mobile Vikings)
- **Voice Balance**: Minutes usage details.
- **SMS Balance**: SMS usage details.
- **Out of Bundle Costs**.
- **SMS Balance**: SMS usage details. (only for Mobile Vikings)
- **Out of Bundle Costs**. (only for Mobile Vikings)
- **Credit Balance**.
- **Subscription Product Information**.
- **SIM Alias**.
Expand Down Expand Up @@ -56,7 +60,7 @@ A Home Assistant integration to monitor Mobile Vikings BE services

## Table of Contents

- [Mobile Vikings for Home Assistant](#mobile-vikings-for-home-assistant)
- [Mobile Vikings and Jim Mobile for Home Assistant](#mobile-vikings--jimmobile-mobile-phone-subscription-information-for-home-assistant)
- [Features](#features)
- [Table of Contents](#table-of-contents)
- [Installation](#installation)
Expand Down Expand Up @@ -102,38 +106,39 @@ This integration will set up the following platforms.
| `mobile_vikings` | Home Assistant component for Mobile Vikings BE services |

## Available Sensors
Support for Mobile Vikings (MV) or Jim Mobile (JM)

### Account Details

| Sensor Key | Description | Unit |
| -------------------------- | ------------------------ | -------- |
| `customer_info` | Customer's first name | Text |
| `loyalty_points_available` | Available loyalty points | € (Euro) |
| `loyalty_points_blocked` | Blocked loyalty points | € (Euro) |
| `loyalty_points_pending` | Pending loyalty points | € (Euro) |
| Sensor Key | Description | Unit | Support |
| -------------------------- | ------------------------ | -------- | ------- |
| `customer_info` | Customer's first name | Text | MV & JM |
| `loyalty_points_available` | Available loyalty points | € (Euro) | MV |
| `loyalty_points_blocked` | Blocked loyalty points | € (Euro) | MV |
| `loyalty_points_pending` | Pending loyalty points | € (Euro) | MV |

### Invoices

| Sensor Key | Description | Unit |
| ------------------------- | ---------------------------- | --------- |
| `paid_invoices` | Total paid invoices | Count |
| `unpaid_invoices` | Unpaid invoices total amount | € (Euro) |
| `next_invoice_expiration` | Next invoice expiration date | Timestamp |
| Sensor Key | Description | Unit | Support |
| ------------------------- | ---------------------------- | --------- | ------- |
| `paid_invoices` | Total paid invoices | Count | MV |
| `unpaid_invoices` | Unpaid invoices total amount | € (Euro) | MV |
| `next_invoice_expiration` | Next invoice expiration date | Timestamp | MV |

### Subscription Details

| Sensor Key | Description | Unit |
| -------------------- | ------------------------------ | ---------- |
| `data_balance` | Data usage percentage | % |
| `data_remaining` | Data remaining | GB |
| `remaining_days` | Days left in billing cycle | Days |
| `period_percentage` | Billing cycle usage percentage | % |
| `voice_balance` | Voice usage percentage | % |
| `sms_balance` | SMS usage percentage | % |
| `out_of_bundle_cost` | Out-of-bundle cost | € (Euro) |
| `credit` | Available credit balance | € (Euro) |
| `product_info` | Subscription product details | Text/Price |
| `sim_alias` | SIM alias | Text |
| Sensor Key | Description | Unit | Support |
| -------------------- | ------------------------------ | ---------- | ------- |
| `data_balance` | Data usage percentage | % | MV |
| `data_remaining` | Data remaining | GB | MV |
| `remaining_days` | Days left in billing cycle | Days | MV & JM |
| `period_percentage` | Billing cycle usage percentage | % | MV & JM |
| `voice_balance` | Voice usage percentage | % | MV & JM |
| `sms_balance` | SMS usage percentage | % | MV |
| `out_of_bundle_cost` | Out-of-bundle cost | € (Euro) | MV & JM |
| `credit` | Available credit balance | € (Euro) | MV & JM |
| `product_info` | Subscription product details | Text/Price | MV & JM |
| `sim_alias` | SIM alias | Text | MV & JM |

---

Expand Down Expand Up @@ -161,8 +166,8 @@ Once you enable debug logging, you ideally need to make the error happen. Run yo

## Code origin

The code of this Home Assistant integration has been written initially by analysing the calls made by the Mobile Vikings website.
The code of this Home Assistant integration has been written initially by analysing the calls made by the Mobile Vikings and Jim Mobile website.

The current version uses the MV Api as documented here: [https://docs.api.unleashed.be/](https://docs.api.unleashed.be/)

I have no link with Mobile Vikings
I have no link with Mobile Vikings or Jim Mobile
8 changes: 5 additions & 3 deletions custom_components/mobile_vikings/__init__.py
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use snake_case (PEP 8) for your namings? So mobilePlatform needs to be mobile_platform

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will update it later today

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No rush :-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all done

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""MobileVikings integration."""
"""MobileVikings/JimMobile integration."""

from __future__ import annotations

Expand All @@ -13,22 +13,24 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .client import MobileVikingsClient
from .const import COORDINATOR_UPDATE_INTERVAL, DOMAIN, PLATFORMS
from .const import COORDINATOR_UPDATE_INTERVAL, DOMAIN, PLATFORMS, MOBILE_VIKINGS

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up MobileVikings from a config entry."""
"""Set up MobileVikings / JimMobile from a config entry."""
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {}

for platform in PLATFORMS:
hass.data[DOMAIN][entry.entry_id].setdefault(platform, set())

client = MobileVikingsClient(
hass=hass,
mobile_platform=entry.data.get('mobile_platform', MOBILE_VIKINGS), # Default to MOBILE_VIKINGS for backward compatibility
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],

)

storage_dir = Path(f"{hass.config.path(STORAGE_DIR)}/{DOMAIN}")
Expand Down
Binary file not shown.
31 changes: 19 additions & 12 deletions custom_components/mobile_vikings/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from homeassistant.util import slugify

from . import MobileVikingsDataUpdateCoordinator
from .const import DOMAIN
from .const import DOMAIN, MOBILE_VIKINGS, JIM_MOBILE
from .entity import MobileVikingsEntity
from .utils import safe_get

Expand All @@ -42,6 +42,7 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
subscription_types: tuple[str, ...] | None = (
None # Optional list of subscription types
)
mobile_platforms: tuple[str, ...] | None = None


SUBSCRIPTION_SENSOR_TYPES: tuple[MobileVikingsBinarySensorDescription, ...] = (
Expand All @@ -53,10 +54,9 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
(data.get("sim") or {}).get("msisdn", "") + "_data_usage_alert"
),
entity_id_prefix_fn=lambda data: "",
available_fn=lambda data: data.get("balance_aggregated", {})
.get("data", {})
.get("usage_alert")
is not None,
available_fn=lambda data: safe_get(
data, ["balance_aggregated", "data", "usage_alert"], default=None
),
value_fn=lambda data: safe_get(
data, ["balance_aggregated", "data", "usage_alert"], default=0
),
Expand All @@ -72,6 +72,7 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
),
device_class=BinarySensorDeviceClass.SAFETY,
icon="mdi:alarm-light",
mobile_platforms=(MOBILE_VIKINGS),
),
MobileVikingsBinarySensorDescription(
key="subscriptions",
Expand All @@ -81,9 +82,9 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
(data.get("sim") or {}).get("msisdn", "") + "_voice_usage_alert"
),
entity_id_prefix_fn=lambda data: "",
available_fn=lambda data: data.get("balance_aggregated", {})
.get("voice", {})
.get("usage_alert")
available_fn=lambda data: safe_get(
data, ["balance_aggregated", "voice", "usage_alert"], default=None
)
is not None,
value_fn=lambda data: safe_get(
data, ["balance_aggregated", "voice", "usage_alert"], default=0
Expand All @@ -100,6 +101,7 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
),
device_class=BinarySensorDeviceClass.SAFETY,
icon="mdi:alarm-light",
mobile_platforms=(MOBILE_VIKINGS),
),
MobileVikingsBinarySensorDescription(
key="subscriptions",
Expand All @@ -109,10 +111,9 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
(data.get("sim") or {}).get("msisdn", "") + "_sms_usage_alert"
),
entity_id_prefix_fn=lambda data: "",
available_fn=lambda data: data.get("balance_aggregated", {})
.get("sms", {})
.get("usage_alert")
is not None,
available_fn=lambda data: safe_get(
data, ["balance_aggregated", "sms", "usage_alert"], default=None
) is not None,
value_fn=lambda data: safe_get(
data, ["balance_aggregated", "sms", "usage_alert"], default=0
),
Expand All @@ -128,6 +129,7 @@ class MobileVikingsBinarySensorDescription(SensorEntityDescription):
),
device_class=BinarySensorDeviceClass.SAFETY,
icon="mdi:alarm-light",
mobile_platforms=(MOBILE_VIKINGS),
),
)

Expand All @@ -143,12 +145,17 @@ async def async_setup_entry(
"coordinator"
]
entities: list[MobileVikingsBinarySensor] = []
mobile_platform = coordinator.client.mobile_platform


for subscription_id, subscription_data in coordinator.data.get(
"subscriptions", []
).items():
# Add static sensors from SUBSCRIPTION_SENSOR_TYPES
for sensor_type in SUBSCRIPTION_SENSOR_TYPES:
if not sensor_type.mobile_platforms or mobile_platform not in sensor_type.mobile_platforms:
_LOGGER.debug(f"Skipping {sensor_type.key}-{sensor_type.translation_key} for mobile platform {mobile_platform}")
continue
_LOGGER.debug(
f"Searching for {sensor_type.key}-{sensor_type.translation_key}"
)
Expand Down
39 changes: 31 additions & 8 deletions custom_components/mobile_vikings/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,18 @@

from homeassistant.helpers.httpx_client import get_async_client

from .const import BASE_URL, CLIENT_ID, CLIENT_SECRET
from .const import (
BASE_URL,
BASE_URL_JIMMOBILE,
CLIENT_ID,
CLIENT_ID_JIMMOBILE,
CLIENT_SECRET,
CLIENT_SECRET_TAG,
CLIENT_SECRET_TAG_JIMMOBILE,
CLIENT_SECRET_VALUE_JIMMOBILE,
MOBILE_VIKINGS,
JIM_MOBILE
)

_LOGGER = logging.getLogger(__name__)

Expand All @@ -19,7 +30,7 @@ class AuthenticationError(Exception):
class MobileVikingsClient:
"""Asynchronous client for interacting with the Mobile Vikings API."""

def __init__(self, hass, username, password, tokens=None):
def __init__(self, hass, username, password, mobile_platform, tokens=None):
"""Initialize the MobileVikingsClient.

Parameters
Expand All @@ -30,13 +41,16 @@ def __init__(self, hass, username, password, tokens=None):
The username for authenticating with the Mobile Vikings API.
password : str
The password for authenticating with the Mobile Vikings API.
mobile_platform : str
The name of the mobile platform to connect to (Mobile Vikings or Jim Mobile).
tokens : dict, optional
A dictionary containing token information (refresh_token, access_token, expiry).

"""
self.hass = hass
self.username = username
self.password = password
self.mobile_platform = mobile_platform
self.refresh_token = None
self.access_token = None
self.expires_in = None
Expand All @@ -55,7 +69,10 @@ async def close(self):
self.client = None

async def authenticate(self):
"""Authenticate with the Mobile Vikings API."""
"""Authenticate with the Mobile Vikings / JimMobile API."""
actual_client_id = CLIENT_ID_JIMMOBILE if self.mobile_platform == JIM_MOBILE else CLIENT_ID
actual_client_secret_tag = CLIENT_SECRET_TAG_JIMMOBILE if self.mobile_platform == JIM_MOBILE else CLIENT_SECRET_TAG
actual_client_secret_value = CLIENT_SECRET_VALUE_JIMMOBILE if self.mobile_platform == JIM_MOBILE else CLIENT_SECRET
if self._is_token_valid():
self.client.headers["Authorization"] = f"Bearer {self.access_token}"
else:
Expand All @@ -65,8 +82,8 @@ async def authenticate(self):
{
"refresh_token": self.refresh_token,
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"client_id": actual_client_id,
actual_client_secret_tag: actual_client_secret_value,
}
)
else:
Expand All @@ -76,8 +93,8 @@ async def authenticate(self):
"username": self.username,
"password": self.password,
"grant_type": "password",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"client_id": actual_client_id,
actual_client_secret_tag: actual_client_secret_value,
}
)

Expand Down Expand Up @@ -137,7 +154,10 @@ async def handle_request(
if authenticate_request is False:
await self.authenticate()

url = BASE_URL + endpoint
if self.mobile_platform == MOBILE_VIKINGS:
url = BASE_URL + endpoint
else:
url = BASE_URL_JIMMOBILE + endpoint
request_details = f"{method} request to: {url}"

# Anonymize sensitive information like passwords
Expand Down Expand Up @@ -204,6 +224,9 @@ async def get_loyalty_points_balance(self):
dict or None: A dictionary containing loyalty points balance, or None if request fails.

"""
if self.mobile_platform == JIM_MOBILE:
# not existing for Jim Mobile
return {'error': 'not existing for Jim Mobile'}
return await self.handle_request("/loyalty-points/balance")

async def get_product_details(self, product_id):
Expand Down
Loading
Loading