Add fields in display and device; add battery, measureBattery
This commit is contained in:
parent
e47748563e
commit
92be9c1aa1
|
@ -27,3 +27,45 @@ To remove children lots the idea is the same:
|
|||
And for devices is all the same:
|
||||
``POST /lots/<parent-lot-id>/devices/?id=<device-id-1>&id=<device-id-2>``;
|
||||
idem for removing devices.
|
||||
|
||||
|
||||
Sharing lots
|
||||
************
|
||||
Sharing a lot means giving certain permissions to users, like reading
|
||||
the characteristics of devices, or performing certain events.
|
||||
|
||||
Linking lots
|
||||
============
|
||||
Linking lots means setting a reference to a lot in another Devicehub
|
||||
which supposedly have the same devices. One of both lots is considered
|
||||
to be the outgoing lot and the other one the ingoing, in the sense
|
||||
that the lifetime of the devices of the outgoing lot precedes the
|
||||
incoming lot; in other way, the events of the devices in the outgoing
|
||||
lot should (but not enforced) happen before that the events in the
|
||||
incoming lot.
|
||||
|
||||
The lot has two fields that uses to keep a link: the Devicehub URL and
|
||||
the ID of the other lot (internal ID I suppose...).
|
||||
|
||||
A device can only be in one incoming lot and in one outgoing lot.
|
||||
|
||||
A device inside a linked lot, when it is confirmed the existence of
|
||||
an equal device in the other DB, is considered to be linked.
|
||||
|
||||
A linked lot defines a both way share of READ Characteristics, at
|
||||
least.
|
||||
|
||||
Users can sync the characteristics of devices + selected tags + selected rules,
|
||||
copying (but not deleting) devices between them. This ensures no
|
||||
traceability warnings when generating the lifetime of devices. In first
|
||||
iterations this process could be done manually (to allow user select
|
||||
tags and rules)
|
||||
|
||||
|
||||
Why linking lots
|
||||
----------------
|
||||
|
||||
* Get the lifetime of devices (reporting). This way a Devicehub
|
||||
traverses the linked devices (as long as it has perm).
|
||||
* Send devices to another Devicehub (characteristics).
|
||||
* Ensure rules over linked devices.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import csv
|
||||
import pathlib
|
||||
from contextlib import suppress
|
||||
from fractions import Fraction
|
||||
from itertools import chain
|
||||
from operator import attrgetter
|
||||
from typing import Dict, List, Set
|
||||
|
@ -12,6 +13,7 @@ from more_itertools import unique_everseen
|
|||
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
||||
Sequence, SmallInteger, Unicode, inspect, text
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from sqlalchemy_utils import ColorType
|
||||
|
@ -23,8 +25,8 @@ from teal.marshmallow import ValidationError
|
|||
from teal.resource import url_for_resource
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
||||
PrinterTechnology, RamFormat, RamInterface, Severity
|
||||
from ereuse_devicehub.resources.enums import BatteryTechnology, ComputerChassis, \
|
||||
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity
|
||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||
|
||||
|
||||
|
@ -58,14 +60,15 @@ class Device(Thing):
|
|||
so it can re-generated *offline*.
|
||||
|
||||
""" + HID_CONVERSION_DOC
|
||||
model = Column(Unicode(), check_lower('model'))
|
||||
model.comment = """The model or brand of the device in lower case.
|
||||
model = Column(Unicode, check_lower('model'))
|
||||
model.comment = """The model of the device in lower case.
|
||||
|
||||
Devices usually report one of both (model or brand). This value
|
||||
must be consistent through time.
|
||||
The model is the unambiguous, as technical as possible, denomination
|
||||
for the product. This field, among others, is used to identify
|
||||
the product.
|
||||
"""
|
||||
manufacturer = Column(Unicode(), check_lower('manufacturer'))
|
||||
manufacturer.comment = """The normalized name of the manufacturer
|
||||
manufacturer.comment = """The normalized name of the manufacturer,
|
||||
in lower case.
|
||||
|
||||
Although as of now Devicehub does not enforce normalization,
|
||||
|
@ -74,26 +77,38 @@ class Device(Thing):
|
|||
"""
|
||||
serial_number = Column(Unicode(), check_lower('serial_number'))
|
||||
serial_number.comment = """The serial number of the device in lower case."""
|
||||
brand = db.Column(CIText())
|
||||
brand.comment = """A naming for consumers. This field can represent
|
||||
several models, so it can be ambiguous, and it is not used to
|
||||
identify the product.
|
||||
"""
|
||||
generation = db.Column(db.SmallInteger, check_range('generation', 0))
|
||||
generation.comment = """The generation of the device."""
|
||||
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
|
||||
weight.comment = """
|
||||
The weight of the device.
|
||||
"""
|
||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
||||
width.comment = """
|
||||
The width of the device.
|
||||
The width of the device in meters.
|
||||
"""
|
||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
||||
height.comment = """
|
||||
The height of the device.
|
||||
The height of the device in meters.
|
||||
"""
|
||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
||||
depth.comment = """
|
||||
The depth of the device.
|
||||
The depth of the device in meters.
|
||||
"""
|
||||
color = Column(ColorType)
|
||||
color.comment = """The predominant color of the device."""
|
||||
production_date = Column(db.TIMESTAMP(timezone=True))
|
||||
production_date.comment = """The date of production of the device."""
|
||||
production_date = Column(db.DateTime)
|
||||
production_date.comment = """The date of production of the device.
|
||||
This is timezone naive, as Workbench cannot report this data
|
||||
with timezone information.
|
||||
"""
|
||||
variant = Column(Unicode)
|
||||
variant.comment = """A variant or sub-model of the device."""
|
||||
|
||||
_NON_PHYSICAL_PROPS = {
|
||||
'id',
|
||||
|
@ -107,7 +122,11 @@ class Device(Thing):
|
|||
'width',
|
||||
'height',
|
||||
'depth',
|
||||
'weight'
|
||||
'weight',
|
||||
'brand',
|
||||
'generation',
|
||||
'production_date',
|
||||
'variant'
|
||||
}
|
||||
|
||||
__table_args__ = (
|
||||
|
@ -129,7 +148,7 @@ class Device(Thing):
|
|||
2. Events performed to a component.
|
||||
3. Events performed to a parent device.
|
||||
|
||||
Events are returned by ascending ``created`` time.
|
||||
Events are returned by descending ``created`` time.
|
||||
"""
|
||||
return sorted(chain(self.events_multiple, self.events_one), key=self.EVENT_SORT_KEY)
|
||||
|
||||
|
@ -260,6 +279,7 @@ class Device(Thing):
|
|||
:raise LookupError: Device has not an event of the given type.
|
||||
"""
|
||||
try:
|
||||
# noinspection PyTypeHints
|
||||
return next(e for e in reversed(self.events) if isinstance(e, types))
|
||||
except StopIteration:
|
||||
raise LookupError('{!r} does not contain events of types {}.'.format(self, types))
|
||||
|
@ -288,12 +308,8 @@ class Device(Thing):
|
|||
|
||||
|
||||
class DisplayMixin:
|
||||
"""
|
||||
Aspect ratio can be computed as in
|
||||
https://github.com/mirukan/whratio/blob/master/whratio/ratio.py and
|
||||
could be a future property.
|
||||
"""
|
||||
size = Column(Float(decimal_return_scale=2), check_range('size', 2, 150))
|
||||
"""Base class for the Display Component and the Monitor Device."""
|
||||
size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=False)
|
||||
size.comment = """
|
||||
The size of the monitor in inches.
|
||||
"""
|
||||
|
@ -301,27 +317,59 @@ class DisplayMixin:
|
|||
technology.comment = """
|
||||
The technology the monitor uses to display the image.
|
||||
"""
|
||||
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000))
|
||||
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
|
||||
nullable=False)
|
||||
resolution_width.comment = """
|
||||
The maximum horizontal resolution the monitor can natively support
|
||||
in pixels.
|
||||
"""
|
||||
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000))
|
||||
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
|
||||
nullable=False)
|
||||
resolution_height.comment = """
|
||||
The maximum vertical resolution the monitor can natively support
|
||||
in pixels.
|
||||
"""
|
||||
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
|
||||
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
|
||||
touchable = Column(Boolean, nullable=False, default=False)
|
||||
touchable = Column(Boolean)
|
||||
touchable.comment = """Whether it is a touchscreen."""
|
||||
|
||||
@hybrid_property
|
||||
def aspect_ratio(self):
|
||||
"""The aspect ratio of the display, as a fraction: ``X/Y``.
|
||||
|
||||
Regular values are ``4/3``, ``5/4``, ``16/9``, ``21/9``,
|
||||
``14/10``, ``19/10``, ``16/10``.
|
||||
"""
|
||||
return Fraction(self.resolution_width, self.resolution_height)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
@aspect_ratio.expression
|
||||
def aspect_ratio(cls):
|
||||
# The aspect ratio to use as SQL in the DB
|
||||
# This allows comparing resolutions
|
||||
return db.func.round(cls.resolution_width / cls.resolution_height, 2)
|
||||
|
||||
@hybrid_property
|
||||
def widescreen(self):
|
||||
"""Whether the monitor is considered to be widescreen.
|
||||
|
||||
Widescreen monitors are those having a higher aspect ratio
|
||||
greater than 4/3.
|
||||
"""
|
||||
# We add a tiny extra to 4/3 to avoid precision errors
|
||||
return self.aspect_ratio > 4.001 / 3
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{0.t} {0.serial_number} {0.size}in ({0.aspect_ratio}) {0.technology}'.format(self)
|
||||
|
||||
def __format__(self, format_spec: str) -> str:
|
||||
v = ''
|
||||
if 't' in format_spec:
|
||||
v += '{0.t} {0.model}'.format(self)
|
||||
if 's' in format_spec:
|
||||
v += '({0.manufacturer}) S/N {0.serial_number} – {0.size}in {0.technology}'
|
||||
v += '({0.manufacturer}) S/N {0.serial_number}'.format(self)
|
||||
v += '– {0.size}in ({0.aspect_ratio}) {0.technology}'.format(self)
|
||||
return v
|
||||
|
||||
|
||||
|
@ -443,14 +491,16 @@ class Mobile(Device):
|
|||
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
imei = Column(BigInteger)
|
||||
imei.comment = """
|
||||
The International Mobile Equipment Identity of the smartphone
|
||||
as an integer.
|
||||
imei.comment = """The International Mobile Equipment Identity of
|
||||
the smartphone as an integer.
|
||||
"""
|
||||
meid = Column(Unicode)
|
||||
meid.comment = """
|
||||
The Mobile Equipment Identifier as a hexadecimal string.
|
||||
meid.comment = """The Mobile Equipment Identifier as a hexadecimal
|
||||
string.
|
||||
"""
|
||||
ram_size = db.Column(db.Integer, check_range(1, ))
|
||||
ram_size.comment = """The total of RAM of the device in MB."""
|
||||
data_storage_size = db.Column(db.Integer)
|
||||
|
||||
@validates('imei')
|
||||
def validate_imei(self, _, value: int):
|
||||
|
@ -576,6 +626,8 @@ class Motherboard(JoinedComponentTableMixin, Component):
|
|||
firewire = Column(SmallInteger, check_range('firewire', min=0))
|
||||
serial = Column(SmallInteger, check_range('serial', min=0))
|
||||
pcmcia = Column(SmallInteger, check_range('pcmcia', min=0))
|
||||
bios_date = Column(db.Date)
|
||||
bios_date.comment = """The date of the BIOS version."""
|
||||
|
||||
|
||||
class NetworkMixin:
|
||||
|
@ -609,6 +661,8 @@ class Processor(JoinedComponentTableMixin, Component):
|
|||
threads.comment = """The number of threads per core."""
|
||||
address = Column(SmallInteger, check_range('address', 8, 256))
|
||||
address.comment = """The address of the CPU: 8, 16, 32, 64, 128 or 256 bits."""
|
||||
abi = Column(Unicode, check_lower('abi'))
|
||||
abi.comment = """The Application Binary Interface of the processor."""
|
||||
|
||||
|
||||
class RamModule(JoinedComponentTableMixin, Component):
|
||||
|
@ -634,6 +688,24 @@ class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
|||
pass
|
||||
|
||||
|
||||
class Battery(JoinedComponentTableMixin, Component):
|
||||
wireless = db.Column(db.Boolean)
|
||||
wireless.comment = """If the battery can be charged wirelessly."""
|
||||
technology = db.Column(db.Enum(BatteryTechnology))
|
||||
size = db.Column(db.Integer, nullable=False)
|
||||
size.comment = """Maximum battery capacity by design, in mAh.
|
||||
|
||||
Use BatteryTest's "size" to get the actual size of the battery.
|
||||
"""
|
||||
|
||||
@property
|
||||
def capacity(self) -> float:
|
||||
"""The quantity of """
|
||||
from ereuse_devicehub.resources.event.models import MeasureBattery
|
||||
real_size = self.last_event_of(MeasureBattery).size
|
||||
return real_size / self.size if real_size and self.size else None
|
||||
|
||||
|
||||
class ComputerAccessory(Device):
|
||||
"""Computer peripherals and similar accessories."""
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from datetime import datetime
|
||||
from fractions import Fraction
|
||||
from operator import attrgetter
|
||||
from typing import Dict, Generator, Iterable, List, Optional, Set, Type
|
||||
from typing import Dict, Generator, Iterable, List, Optional, Set, Type, TypeVar
|
||||
|
||||
from boltons import urlutils
|
||||
from boltons.urlutils import URL
|
||||
|
@ -12,15 +13,16 @@ from teal.enums import Layouts
|
|||
|
||||
from ereuse_devicehub.resources.agent.models import Agent
|
||||
from ereuse_devicehub.resources.device import states
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
||||
PrinterTechnology, RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.enums import BatteryTechnology, ComputerChassis, \
|
||||
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface
|
||||
from ereuse_devicehub.resources.event import models as e
|
||||
from ereuse_devicehub.resources.image.models import ImageList
|
||||
from ereuse_devicehub.resources.lot.models import Lot
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
from ereuse_devicehub.resources.tag import Tag
|
||||
from ereuse_devicehub.resources.tag.model import Tags
|
||||
|
||||
E = TypeVar('E', bound=e.Event)
|
||||
|
||||
|
||||
class Device(Thing):
|
||||
EVENT_SORT_KEY = attrgetter('created')
|
||||
|
@ -38,27 +40,32 @@ class Device(Thing):
|
|||
color = ... # type: Column
|
||||
lots = ... # type: relationship
|
||||
production_date = ... # type: Column
|
||||
brand = ... # type: Column
|
||||
generation = ... # type: Column
|
||||
variant = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.id = ... # type: int
|
||||
self.type = ... # type: str
|
||||
self.hid = ... # type: str
|
||||
self.model = ... # type: str
|
||||
self.manufacturer = ... # type: str
|
||||
self.serial_number = ... # type: str
|
||||
self.weight = ... # type: float
|
||||
self.width = ... # type:float
|
||||
self.height = ... # type: float
|
||||
self.depth = ... # type: float
|
||||
self.color = ... # type: Color
|
||||
self.hid = ... # type: Optional[str]
|
||||
self.model = ... # type: Optional[str]
|
||||
self.manufacturer = ... # type: Optional[str]
|
||||
self.serial_number = ... # type: Optional[str]
|
||||
self.weight = ... # type: Optional[float]
|
||||
self.width = ... # type:Optional[float]
|
||||
self.height = ... # type: Optional[float]
|
||||
self.depth = ... # type: Optional[float]
|
||||
self.color = ... # type: Optional[Color]
|
||||
self.physical_properties = ... # type: Dict[str, object or None]
|
||||
self.events_multiple = ... # type: Set[e.EventWithMultipleDevices]
|
||||
self.events_one = ... # type: Set[e.EventWithOneDevice]
|
||||
self.images = ... # type: ImageList
|
||||
self.tags = ... # type: Tags[Tag]
|
||||
self.lots = ... # type: Set[Lot]
|
||||
self.production_date = ... # type: datetime
|
||||
self.production_date = ... # type: Optional[datetime]
|
||||
self.brand = ... # type: Optional[str]
|
||||
self.generation = ... # type: Optional[int]
|
||||
self.variant = ... # type: Optional[str]
|
||||
|
||||
@property
|
||||
def events(self) -> List[e.Event]:
|
||||
|
@ -96,7 +103,7 @@ class Device(Thing):
|
|||
def working(self) -> List[e.Test]:
|
||||
pass
|
||||
|
||||
def last_event_of(self, *types: Type[e.Event]) -> e.Event:
|
||||
def last_event_of(self, *types: Type[E]) -> E:
|
||||
pass
|
||||
|
||||
def _warning_events(self, events: Iterable[e.Event]) -> Generator[e.Event]:
|
||||
|
@ -118,9 +125,11 @@ class DisplayMixin:
|
|||
self.size = ... # type: Integer
|
||||
self.resolution_width = ... # type: int
|
||||
self.resolution_height = ... # type: int
|
||||
self.refresh_rate = ... # type: int
|
||||
self.contrast_ratio = ... # type: int
|
||||
self.touchable = ... # type: bool
|
||||
self.refresh_rate = ... # type: Optional[int]
|
||||
self.contrast_ratio = ... # type: Optional[int]
|
||||
self.touchable = ... # type: Optional[bool]
|
||||
self.aspect_ratio = ... #type: Fraction
|
||||
self.widescreen = ... # type: bool
|
||||
|
||||
|
||||
class Computer(DisplayMixin, Device):
|
||||
|
@ -193,11 +202,15 @@ class TelevisionSet(Monitor):
|
|||
class Mobile(Device):
|
||||
imei = ... # type: Column
|
||||
meid = ... # type: Column
|
||||
ram_size = ... # type: Column
|
||||
data_storage_size = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.imei = ... # type: int
|
||||
self.meid = ... # type: str
|
||||
self.imei = ... # type: Optional[int]
|
||||
self.meid = ... # type: Optional[str]
|
||||
self.ram_size = ... # type: Optional[int]
|
||||
self.data_storage_size = ... # type: Optional[int]
|
||||
|
||||
|
||||
class Smartphone(Mobile):
|
||||
|
@ -288,13 +301,15 @@ class Processor(Component):
|
|||
cores = ... # type: Column
|
||||
address = ... # type: Column
|
||||
threads = ... # type: Column
|
||||
abi = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.speed = ... # type: float
|
||||
self.cores = ... # type: int
|
||||
self.threads = ... # type: int
|
||||
self.address = ... # type: int
|
||||
self.speed = ... # type: Optional[float]
|
||||
self.cores = ... # type: Optional[int]
|
||||
self.threads = ... # type: Optional[int]
|
||||
self.address = ... # type: Optional[int]
|
||||
self.abi = ... # type: Optional[str]
|
||||
|
||||
|
||||
class RamModule(Component):
|
||||
|
@ -319,6 +334,18 @@ class Display(DisplayMixin, Component):
|
|||
pass
|
||||
|
||||
|
||||
class Battery(Component):
|
||||
wireless = ... # type: Column
|
||||
technology = ... # type: Column
|
||||
size = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.wireless = ... # type: Optional[bool]
|
||||
self.technology = ... # type: Optional[BatteryTechnology]
|
||||
self.size = ... # type: bool
|
||||
|
||||
|
||||
class ComputerAccessory(Device):
|
||||
pass
|
||||
|
||||
|
|
|
@ -22,9 +22,17 @@ class Device(Thing):
|
|||
many=True,
|
||||
collection_class=OrderedSet,
|
||||
description='A set of tags that identify the device.')
|
||||
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
|
||||
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
||||
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
||||
model = SanitizedStr(lower=True,
|
||||
validate=Length(max=STR_BIG_SIZE),
|
||||
description=m.Device.model.comment)
|
||||
manufacturer = SanitizedStr(lower=True,
|
||||
validate=Length(max=STR_SIZE),
|
||||
description=m.Device.manufacturer.comment)
|
||||
serial_number = SanitizedStr(lower=True,
|
||||
validate=Length(max=STR_BIG_SIZE),
|
||||
data_key='serialNumber')
|
||||
brand = SanitizedStr(validate=Length(max=STR_BIG_SIZE), description=m.Device.brand.comment)
|
||||
generation = Integer(validate=Range(1, 100), description=m.Device.generation.comment)
|
||||
weight = Float(validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment)
|
||||
width = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment)
|
||||
height = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment)
|
||||
|
@ -113,8 +121,8 @@ class Desktop(Computer):
|
|||
|
||||
|
||||
class Laptop(Computer):
|
||||
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
||||
__doc__ = m.Laptop.__doc__
|
||||
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
||||
|
||||
|
||||
class Server(Computer):
|
||||
|
@ -123,19 +131,22 @@ class Server(Computer):
|
|||
|
||||
class DisplayMixin:
|
||||
__doc__ = m.DisplayMixin.__doc__
|
||||
|
||||
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
|
||||
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150), required=True)
|
||||
technology = EnumField(enums.DisplayTech,
|
||||
description=m.DisplayMixin.technology.comment)
|
||||
resolution_width = Integer(data_key='resolutionWidth',
|
||||
validate=Range(10, 20000),
|
||||
description=m.DisplayMixin.resolution_width.comment)
|
||||
description=m.DisplayMixin.resolution_width.comment,
|
||||
required=True)
|
||||
resolution_height = Integer(data_key='resolutionHeight',
|
||||
validate=Range(10, 20000),
|
||||
description=m.DisplayMixin.resolution_height.comment)
|
||||
description=m.DisplayMixin.resolution_height.comment,
|
||||
required=True)
|
||||
refresh_rate = Integer(data_key='refreshRate', validate=Range(10, 1000))
|
||||
contrast_ratio = Integer(data_key='contrastRatio', validate=Range(100, 100000))
|
||||
touchable = Boolean(missing=False, description=m.DisplayMixin.touchable.comment)
|
||||
touchable = Boolean(description=m.DisplayMixin.touchable.comment)
|
||||
aspect_ratio = String(dump_only=True, description=m.DisplayMixin.aspect_ratio.__doc__)
|
||||
widescreen = Boolean(dump_only=True, description=m.DisplayMixin.widescreen.__doc__)
|
||||
|
||||
|
||||
class NetworkMixin:
|
||||
|
@ -164,6 +175,14 @@ class Mobile(Device):
|
|||
|
||||
imei = Integer(description=m.Mobile.imei.comment)
|
||||
meid = Str(description=m.Mobile.meid.comment)
|
||||
ram_size = Integer(validate=Range(min=128, max=36000),
|
||||
data_key='ramSize',
|
||||
unit=UnitCodes.mbyte,
|
||||
description=m.Mobile.ram_size.comment)
|
||||
data_storage_size = Integer(validate=Range(0, 10 ** 8),
|
||||
data_key='dataStorageSize',
|
||||
description=m.Mobile.data_storage_size)
|
||||
|
||||
|
||||
@pre_load
|
||||
def convert_check_imei(self, data):
|
||||
|
@ -247,6 +266,7 @@ class Processor(Component):
|
|||
threads = Integer(validate=Range(min=1, max=20), description=m.Processor.threads.comment)
|
||||
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}),
|
||||
description=m.Processor.address.comment)
|
||||
abi = SanitizedStr(lower=True, description=m.Processor.abi.comment)
|
||||
|
||||
|
||||
class RamModule(Component):
|
||||
|
@ -268,6 +288,14 @@ class Display(DisplayMixin, Component):
|
|||
__doc__ = m.Display.__doc__
|
||||
|
||||
|
||||
class Battery(Component):
|
||||
__doc__ = m.Battery
|
||||
|
||||
wireless = Boolean(description=m.Battery.wireless.comment)
|
||||
technology = EnumField(enums.BatteryTechnology, description=m.Battery.technology.comment)
|
||||
size = Integer(required=True, description=m.Battery.size.comment)
|
||||
|
||||
|
||||
class Manufacturer(Schema):
|
||||
__doc__ = m.Manufacturer.__doc__
|
||||
|
||||
|
|
|
@ -276,6 +276,26 @@ class PrinterTechnology(Enum):
|
|||
Thermal = 'Thermal'
|
||||
|
||||
|
||||
@unique
|
||||
class BatteryHealth(Enum):
|
||||
"""The battery health status as in Android."""
|
||||
Cold = 'Cold'
|
||||
Dead = 'Dead'
|
||||
Good = 'Good'
|
||||
Overheat = 'Overheat'
|
||||
OverVoltage = 'OverVoltage'
|
||||
UnspecifiedValue = 'UnspecifiedValue'
|
||||
|
||||
|
||||
@unique
|
||||
class BatteryTechnology(Enum):
|
||||
"""The technology of the Battery."""
|
||||
LiIon = 'Lithium-ion'
|
||||
NiCad = 'Nickel-Cadmium'
|
||||
NiMH = 'Nickel-metal hydride'
|
||||
Al = 'Alkaline'
|
||||
|
||||
|
||||
class Severity(IntEnum):
|
||||
"""A flag evaluating the event execution. Ex. failed events
|
||||
have the value `Severity.Error`. Devicehub uses 4 severity levels:
|
||||
|
|
|
@ -28,10 +28,10 @@ from ereuse_devicehub.db import db
|
|||
from ereuse_devicehub.resources.agent.models import Agent
|
||||
from ereuse_devicehub.resources.device.models import Component, Computer, DataStorage, Desktop, \
|
||||
Device, Laptop, Server
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ErasureStandards, \
|
||||
FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RATE_NEGATIVE, RATE_POSITIVE, \
|
||||
RatingRange, RatingSoftware, ReceiverRole, Severity, SnapshotExpectedEvents, SnapshotSoftware, \
|
||||
TestDataStorageLength
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, Bios, \
|
||||
ErasureStandards, FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RATE_NEGATIVE, \
|
||||
RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, Severity, SnapshotExpectedEvents, \
|
||||
SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
|
@ -384,7 +384,9 @@ class ErasePhysical(EraseBasic):
|
|||
|
||||
|
||||
class Step(db.Model):
|
||||
erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), primary_key=True)
|
||||
erasure_id = Column(UUID(as_uuid=True),
|
||||
ForeignKey(EraseBasic.id, ondelete='CASCADE'),
|
||||
primary_key=True)
|
||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||
num = Column(SmallInteger, primary_key=True)
|
||||
severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False)
|
||||
|
@ -977,7 +979,13 @@ class Test(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
return args
|
||||
|
||||
|
||||
class TestDataStorage(Test):
|
||||
class TestMixin:
|
||||
@declared_attr
|
||||
def id(cls):
|
||||
return Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||
|
||||
|
||||
class TestDataStorage(TestMixin, Test):
|
||||
"""
|
||||
The act of testing the data storage.
|
||||
|
||||
|
@ -989,7 +997,6 @@ class TestDataStorage(Test):
|
|||
The test takes to other SMART values indicators of the overall health
|
||||
of the data storage.
|
||||
"""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||
length = Column(DBEnum(TestDataStorageLength), nullable=False) # todo from type
|
||||
status = Column(Unicode(), check_lower('status'), nullable=False)
|
||||
lifetime = Column(Interval)
|
||||
|
@ -1034,6 +1041,25 @@ class TestDataStorage(Test):
|
|||
self._reported_uncorrectable_errors = min(value, db.PSQL_INT_MAX)
|
||||
|
||||
|
||||
class MeasureBattery(TestMixin, Test):
|
||||
"""A sample of the status of the battery.
|
||||
|
||||
Operative Systems keep a record of several aspects of a battery.
|
||||
This is a sample of those.
|
||||
"""
|
||||
size = db.Column(db.Integer, nullable=False)
|
||||
size.comment = """Maximum battery capacity, in mAh."""
|
||||
voltage = db.Column(db.Integer, nullable=False)
|
||||
voltage.comment = """The actual voltage of the battery, in mV."""
|
||||
cycle_count = db.Column(db.Integer)
|
||||
cycle_count.comment = """The number of full charges – discharges
|
||||
cycles.
|
||||
"""
|
||||
health = db.Column(db.Enum(BatteryHealth))
|
||||
health.comment = """The health of the Battery.
|
||||
Only reported in Android.
|
||||
"""
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
"""The act of stressing (putting to the maximum capacity)
|
||||
|
@ -1071,9 +1097,14 @@ class Benchmark(JoinedWithOneDeviceMixin, EventWithOneDevice):
|
|||
return args
|
||||
|
||||
|
||||
class BenchmarkDataStorage(Benchmark):
|
||||
class BenchmarkMixin:
|
||||
@declared_attr
|
||||
def id(cls):
|
||||
return Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
|
||||
|
||||
class BenchmarkDataStorage(BenchmarkMixin, Benchmark):
|
||||
"""Benchmarks the data storage unit reading and writing speeds."""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
read_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||
write_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||
|
||||
|
@ -1081,9 +1112,8 @@ class BenchmarkDataStorage(Benchmark):
|
|||
return 'Read: {} MB/s, write: {} MB/s'.format(self.read_speed, self.write_speed)
|
||||
|
||||
|
||||
class BenchmarkWithRate(Benchmark):
|
||||
class BenchmarkWithRate(BenchmarkMixin, Benchmark):
|
||||
"""The act of benchmarking a device with a single rate."""
|
||||
id = Column(UUID(as_uuid=True), ForeignKey(Benchmark.id), primary_key=True)
|
||||
rate = Column(Float, nullable=False)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
|
@ -16,9 +16,9 @@ from teal.enums import Country
|
|||
|
||||
from ereuse_devicehub.resources.agent.models import Agent
|
||||
from ereuse_devicehub.resources.device.models import Component, Computer, Device
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, ErasureStandards, \
|
||||
FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RatingSoftware, ReceiverRole, \
|
||||
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, Bios, \
|
||||
ErasureStandards, FunctionalityRange, PhysicalErasureMethod, PriceSoftware, RatingSoftware, \
|
||||
ReceiverRole, Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
|
@ -357,6 +357,20 @@ class TestDataStorage(Test):
|
|||
self.remaining_lifetime_percentage = ... # type: int
|
||||
|
||||
|
||||
class MeasureBattery(Test):
|
||||
size = ... # type: Column
|
||||
voltage = ... # type: Column
|
||||
cycle_count = ... # type: Column
|
||||
health = ... # type: Column
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.size = ... # type: int
|
||||
self.voltage = ... # type: int
|
||||
self.cycle_count = ... # type: Optional[int]
|
||||
self.health = ... # type: Optional[BatteryHealth]
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
pass
|
||||
|
||||
|
|
|
@ -310,6 +310,14 @@ class TestDataStorage(Test):
|
|||
remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage')
|
||||
|
||||
|
||||
class MeasureBattery(Test):
|
||||
__doc__ = m.MeasureBattery.__doc__
|
||||
size = Integer(required=True, description=m.MeasureBattery.size.comment)
|
||||
voltage = Integer(required=True, description=m.MeasureBattery.voltage.comment)
|
||||
cycle_count = Integer(required=True, description=m.MeasureBattery.cycle_count.comment)
|
||||
health = EnumField(enums.BatteryHealth, description=m.MeasureBattery.health.comment)
|
||||
|
||||
|
||||
class StressTest(Test):
|
||||
__doc__ = m.StressTest.__doc__
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ def test_physical_properties():
|
|||
'serial': None,
|
||||
'firewire': None,
|
||||
'manufacturer': 'mr',
|
||||
'bios_date': None
|
||||
}
|
||||
assert pc.physical_properties == {
|
||||
'model': 'foo',
|
||||
|
@ -454,11 +455,6 @@ def test_computer_monitor():
|
|||
db.session.commit()
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='Make test')
|
||||
def test_computer_with_display():
|
||||
pass
|
||||
|
||||
|
||||
def test_manufacturer(user: UserClient):
|
||||
m, r = user.get(res='Manufacturer', query=[('search', 'asus')])
|
||||
assert m == {'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]}
|
||||
|
|
Reference in a new issue