diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/SystemBattery.vue b/platypush/backend/http/webapp/src/components/panels/Entities/SystemBattery.vue new file mode 120000 index 000000000..ed4eb6014 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Entities/SystemBattery.vue @@ -0,0 +1 @@ +Battery.vue \ No newline at end of file diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json index 087ce71a4..d0c504a62 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/meta.json +++ b/platypush/backend/http/webapp/src/components/panels/Entities/meta.json @@ -79,14 +79,6 @@ } }, - "current_sensor": { - "name": "Sensor", - "name_plural": "Sensors", - "icon": { - "class": "fas fa-bolt" - } - }, - "system_fan": { "name": "System", "name_plural": "System", @@ -95,6 +87,22 @@ } }, + "system_battery": { + "name": "System", + "name_plural": "System", + "icon": { + "class": "fas fa-battery-full" + } + }, + + "current_sensor": { + "name": "Sensor", + "name_plural": "Sensors", + "icon": { + "class": "fas fa-bolt" + } + }, + "cpu": { "name": "System", "name_plural": "System", diff --git a/platypush/entities/system.py b/platypush/entities/system.py index 78df645c6..82744b517 100644 --- a/platypush/entities/system.py +++ b/platypush/entities/system.py @@ -1,10 +1,10 @@ -from sqlalchemy import Column, Float, ForeignKey, Integer, JSON, String +from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer, JSON, String from platypush.common.db import Base from . import Entity from .devices import Device -from .sensors import NumericSensor +from .sensors import NumericSensor, PercentSensor from .temperature import TemperatureSensor @@ -253,3 +253,26 @@ if 'system_fan' not in Base.metadata: __mapper_args__ = { 'polymorphic_identity': __tablename__, } + + +if 'system_battery' not in Base.metadata: + + class SystemBattery(PercentSensor): + """ + ``SystemBattery`` ORM model. + """ + + __tablename__ = 'system_battery' + + id = Column( + Integer, + ForeignKey(PercentSensor.id, ondelete='CASCADE'), + primary_key=True, + ) + + seconds_left = Column(Float) + power_plugged = Column(Boolean) + + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + } diff --git a/platypush/message/response/system/__init__.py b/platypush/message/response/system/__init__.py index d37350660..f08458588 100644 --- a/platypush/message/response/system/__init__.py +++ b/platypush/message/response/system/__init__.py @@ -8,25 +8,6 @@ class SystemResponse(Response): pass -class SensorResponse(SystemResponse): - pass - - -class SensorBatteryResponse(SensorResponse): - def __init__( - self, percent: float, secs_left: int, power_plugged: bool, *args, **kwargs - ): - super().__init__( - *args, - output={ - 'percent': percent, - 'secs_left': secs_left, - 'power_plugged': power_plugged, - }, - **kwargs - ) - - class ConnectUserResponse(SystemResponse): def __init__( self, diff --git a/platypush/plugins/system/__init__.py b/platypush/plugins/system/__init__.py index e2cb6a75f..b200bccdd 100644 --- a/platypush/plugins/system/__init__.py +++ b/platypush/plugins/system/__init__.py @@ -19,11 +19,11 @@ from platypush.entities.system import ( MemoryStats as MemoryStatsModel, NetworkInterface as NetworkInterfaceModel, SwapStats as SwapStatsModel, + SystemBattery, SystemFan, SystemTemperature, ) from platypush.message.response.system import ( - SensorBatteryResponse, ConnectedUserResponseList, ConnectUserResponse, ProcessResponseList, @@ -32,6 +32,8 @@ from platypush.message.response.system import ( from platypush.plugins import action from platypush.plugins.sensor import SensorPlugin from platypush.schemas.system import ( + Battery, + BatterySchema, ConnectionSchema, CpuFrequency, CpuFrequencySchema, @@ -387,19 +389,19 @@ class SystemPlugin(SensorPlugin, EntityManager): """ return FanSchema().dump(self._sensors_fan(), many=True) + def _sensors_battery(self) -> Optional[Battery]: + battery = psutil.sensors_battery() + return BatterySchema().load(battery) if battery else None # type: ignore + @action - def sensors_battery(self) -> SensorBatteryResponse: + def sensors_battery(self) -> Optional[dict]: """ Get stats from the battery sensor. - :return: List of :class:`platypush.message.response.system.SensorFanResponse`. - """ - stats = psutil.sensors_battery() - return SensorBatteryResponse( - percent=stats.percent, - secs_left=stats.secsleft, - power_plugged=stats.power_plugged, - ) + :return: .. schema:: system.BatterySchema + """ + battery = self._sensors_battery() + return BatterySchema().dump(battery) if battery else None # type: ignore @action def connected_users(self) -> ConnectedUserResponseList: @@ -554,12 +556,14 @@ class SystemPlugin(SensorPlugin, EntityManager): 'network': self._network_info(), 'temperature': self._sensors_temperature(), 'fans': self._sensors_fan(), + 'battery': self._sensors_battery(), } ) @override def transform_entities(self, entities: dict) -> List[Entity]: cpu = entities['cpu'].copy() + battery = entities['battery'] return [ Cpu( @@ -663,6 +667,15 @@ class SystemPlugin(SensorPlugin, EntityManager): ) for fan in entities.get('fans', []) ], + *[ + SystemBattery( + id='system:battery', + name='Battery', + **battery, + ) + if battery + else () + ], ] diff --git a/platypush/schemas/system/__init__.py b/platypush/schemas/system/__init__.py index 9a2a876ea..437b6ef64 100644 --- a/platypush/schemas/system/__init__.py +++ b/platypush/schemas/system/__init__.py @@ -1,3 +1,4 @@ +from ._battery import Battery, BatterySchema from ._connection import Connection, ConnectionSchema from ._cpu import ( Cpu, @@ -20,6 +21,8 @@ from ._temperature import Temperature, TemperatureSchema __all__ = [ + "Battery", + "BatterySchema", "Connection", "ConnectionSchema", "Cpu", diff --git a/platypush/schemas/system/_battery/__init__.py b/platypush/schemas/system/_battery/__init__.py new file mode 100644 index 000000000..3e5e4e250 --- /dev/null +++ b/platypush/schemas/system/_battery/__init__.py @@ -0,0 +1,4 @@ +from ._model import Battery +from ._schemas import BatterySchema + +__all__ = ['Battery', 'BatterySchema'] diff --git a/platypush/schemas/system/_battery/_base.py b/platypush/schemas/system/_battery/_base.py new file mode 100644 index 000000000..33e1603cd --- /dev/null +++ b/platypush/schemas/system/_battery/_base.py @@ -0,0 +1,20 @@ +from enum import Enum + +from marshmallow import pre_load + +from .._base import SystemBaseSchema + + +class BatteryBaseSchema(SystemBaseSchema): + """ + Base schema for system battery sensors. + """ + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + data = super().pre_load(data) + percent = data.pop('percent', data.pop('value', None)) + seconds_left = data.pop('secsleft', data.pop('seconds_left', None)) + data['value'] = percent / 100 if percent is not None else None + data['seconds_left'] = None if isinstance(seconds_left, Enum) else seconds_left + return data diff --git a/platypush/schemas/system/_battery/_model.py b/platypush/schemas/system/_battery/_model.py new file mode 100644 index 000000000..cd35e479c --- /dev/null +++ b/platypush/schemas/system/_battery/_model.py @@ -0,0 +1,39 @@ +from dataclasses import dataclass, field +from typing import Optional + +from platypush.schemas.dataclasses import percent_field + + +@dataclass +class Battery: + """ + System battery sensor wrapper. + """ + + seconds_left: Optional[float] = field( + metadata={ + 'metadata': { + 'description': 'High threshold for the temperature sensor, in Celsius', + 'example': 75, + } + } + ) + + power_plugged: Optional[bool] = field( + metadata={ + 'metadata': { + 'description': 'Whether the battery is plugged in or not', + 'example': False, + } + } + ) + + value: Optional[float] = percent_field( + metadata={ + 'metadata': { + 'description': 'Current charge left, as a percentage value ' + 'between 0 and 1', + 'example': 0.5, + } + } + ) diff --git a/platypush/schemas/system/_battery/_schemas.py b/platypush/schemas/system/_battery/_schemas.py new file mode 100644 index 000000000..7a6e45897 --- /dev/null +++ b/platypush/schemas/system/_battery/_schemas.py @@ -0,0 +1,7 @@ +from marshmallow_dataclass import class_schema + +from ._base import BatteryBaseSchema +from ._model import Battery + + +BatterySchema = class_schema(Battery, base_schema=BatteryBaseSchema) diff --git a/platypush/schemas/system/_model.py b/platypush/schemas/system/_model.py index 342dd58e0..6cae74be6 100644 --- a/platypush/schemas/system/_model.py +++ b/platypush/schemas/system/_model.py @@ -1,6 +1,7 @@ from dataclasses import dataclass -from typing import List +from typing import List, Optional +from ._battery import Battery from ._cpu import Cpu from ._disk import Disk from ._fan import Fan @@ -22,3 +23,4 @@ class SystemInfo: network: List[NetworkInterface] temperature: List[Temperature] fans: List[Fan] + battery: Optional[Battery]