Skip to content

Commit

Permalink
Import project
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRealKillaruna committed Dec 30, 2023
0 parents commit 0328d26
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 0 deletions.
58 changes: 58 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.3.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
args:
- --safe
- --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v1.16.0
hooks:
- id: codespell
args:
- --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.1
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.5.0
- pydocstyle==5.0.2
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.6.2
hooks:
- id: bandit
args:
- --quiet
- --format=custom
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: check-executables-have-shebangs
stages: [manual]
- id: check-json
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.770
hooks:
- id: mypy
args:
- --pretty
- --show-error-codes
- --show-error-context
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# PJLink2 for Home Assistant

## Installation
Empty file added custom_components/__init__.py
Empty file.
1 change: 1 addition & 0 deletions custom_components/pjlink2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""PJLink2 Custom Component."""
21 changes: 21 additions & 0 deletions custom_components/pjlink2/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Provides the constants needed for component."""
from enum import StrEnum

DOMAIN = "pjlink2"

CONF_ENCODING = "encoding"
DEFAULT_ENCODING = "utf-8"
DEFAULT_PORT = 4352
DEFAULT_TIMEOUT = 4

ATTR_PRODUCT_NAME = "product_name"
ATTR_MANUFACTURER_NAME = "manufacturer_name"
ATTR_PROJECTOR_NAME = "projector_name"
ATTR_RESOLUTION_X = "x_resolution"
ATTR_RESOLUTION_Y = "y_resolution"

class ProjectorState(StrEnum):
OFF = "off"
ON = "on"
COOLING = "cooling"
WARMING = "warming"
11 changes: 11 additions & 0 deletions custom_components/pjlink2/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"codeowners": ["@TheRealKillaruna"],
"config_flow": false,
"dependencies": [],
"documentation": "https://github.com/TheRealKillaruna/pjlink2",
"domain": "pjlink2",
"iot_class": "calculated",
"name": "PJLink2",
"requirements": ["aiopjlink==1.0.5"],
"version": "0.1"
}
143 changes: 143 additions & 0 deletions custom_components/pjlink2/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"""GitHub sensor platform."""
from __future__ import annotations

from collections.abc import Callable
from datetime import timedelta
import logging
from typing import Any

from aiopjlink import PJLink, PJLinkException, PJLinkProjectorError, Power, Sources, Lamp, Information

from homeassistant import config_entries, core
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_NAME, CONF_PASSWORD, CONF_TIMEOUT

import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, HomeAssistantType

import voluptuous as vol

from .const import DOMAIN, CONF_ENCODING, DEFAULT_ENCODING, DEFAULT_PORT, DEFAULT_TIMEOUT, ATTR_PRODUCT_NAME, ATTR_MANUFACTURER_NAME, ATTR_PROJECTOR_NAME, ATTR_RESOLUTION_X, ATTR_RESOLUTION_Y, ProjectorState


_LOGGER = logging.getLogger(__name__)
# Time between updating data from projector
SCAN_INTERVAL = timedelta(seconds=3)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
vol.Optional(CONF_PASSWORD) : cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT) : cv.positive_float
}
)


async def async_setup_platform(
hass: HomeAssistantType,
config: ConfigType,
async_add_entities: Callable,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the sensor platform."""
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
password = config.get(CONF_PASSWORD)
timeout = config.get(CONF_TIMEOUT)
name = config.get(CONF_NAME)
pjl = PJLink(host, port, password, timeout)
sensors = [PJLink2Sensor(pjl, name)]
async_add_entities(sensors, update_before_add=True)


class PJLink2Sensor(Entity):
"""Representation of a PJLink2 sensor."""

def __init__(self, pjl, name):
super().__init__()
self._projector = pjl
self.attrs: dict[str, Any] = {}
self._name = name
self._state = None
self._available = False

async def async_will_remove_from_hass(self) -> None:
"""Close connection."""
await super().async_will_remove_from_hass()
try:
await self._projector.__aexit__(0,0,0)
except PJLinkException as err:
_LOGGER.exception("PJLink2 ERROR for %s: %s", self._name, repr(err))
else:
_LOGGER.info("PJLink2 INFO for %s: Connection closed.", self._name)

@property
def name(self) -> str:
"""Return the name of the entity."""
return self._name

@property
def unique_id(self) -> str:
"""Return the unique ID of the sensor."""
return self._projector._address

@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._available

@property
def state(self) -> str | None:
return self._state

@property
def extra_state_attributes(self) -> dict[str, Any]:
return self.attrs

async def async_update(self) -> None:
"""Update all sensors."""
try:
if not self._available:
# connect and init static information
await self._projector.__aenter__()
self._available = True
info = await Information(self._projector).table()
self.attrs[ATTR_PRODUCT_NAME] = info["product_name"]
self.attrs[ATTR_MANUFACTURER_NAME] = info["manufacturer_name"]
self.attrs[ATTR_PROJECTOR_NAME] = info["projector_name"]
if self._name == None: self._name = info["projector_name"]
_LOGGER.info("PJLink2 INFO for %s: Connection opened.", self._name)

pwr = await Power(self._projector).get()
if pwr == Power.State.OFF: self._state = ProjectorState.OFF
elif pwr == Power.State.ON: self._state = ProjectorState.ON
elif pwr == Power.State.COOLING: self._state = ProjectorState.COOLING
elif pwr == Power.State.WARMING: self._state = ProjectorState.WARMING

if pwr==Power.ON:
res = await Sources(self._projector).resolution()
self.attrs[ATTR_RESOLUTION_X] = res[0]
self.attrs[ATTR_RESOLUTION_Y] = res[1]
else:
if ATTR_RESOLUTION_X in self.attrs: del self.attrs[ATTR_RESOLUTION_X]
if ATTR_RESOLUTION_Y in self.attrs: del self.attrs[ATTR_RESOLUTION_Y]

except PJLinkProjectorError:
# resolution cannot be queried due to no input
if ATTR_RESOLUTION_X in self.attrs: del self.attrs[ATTR_RESOLUTION_X]
if ATTR_RESOLUTION_Y in self.attrs: del self.attrs[ATTR_RESOLUTION_Y]
_LOGGER.info("PJLink2 INFO for %s: Cannot get resolution", self._name)
except PJLinkException as err:
self._state = None
self._available = False
_LOGGER.exception("PJLink2 ERROR for %s: %s", self._name, repr(err))
try:
await self._projector.__aexit__(0,0,0)
except PJLinkException as err:
_LOGGER.exception("PJLink2 ERROR for %s: %s", self._name, repr(err))
else:
_LOGGER.info("PJLink2 INFO for %s: Connection closed.", self._name)
5 changes: 5 additions & 0 deletions hacs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "PJLink2",
"render_readme": true,
"iot_class": "calculated"
}
3 changes: 3 additions & 0 deletions requirements.test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest
pytest-cov==2.9.0
pytest-homeassistant-custom-component
62 changes: 62 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[coverage:run]
source =
custom_components

[coverage:report]
exclude_lines =
pragma: no cover
raise NotImplemented()
if __name__ == '__main__':
main()
show_missing = true

[tool:pytest]
testpaths = tests
norecursedirs = .git
addopts =
--strict
--cov=custom_components

[flake8]
# https://github.com/ambv/black#line-length
max-line-length = 88
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# W504 line break after binary operator
ignore =
E501,
W503,
E203,
D202,
W504

[isort]
# https://github.com/timothycrosley/isort
# https://github.com/timothycrosley/isort/wiki/isort-Settings
# splits long import on multiple lines indented by 4 spaces
multi_line_output = 3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88
indent = " "
# by default isort don't check module indexes
not_skip = __init__.py
# will group `import x` and `from x import` of the same module.
force_sort_within_sections = true
sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
default_section = THIRDPARTY
known_first_party = custom_components,tests
forced_separate = tests
combine_as_imports = true

[mypy]
python_version = 3.7
ignore_errors = true
follow_imports = silent
ignore_missing_imports = true
warn_incomplete_stub = true
warn_redundant_casts = true
warn_unused_configs = true
Empty file added tests/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions tests/bandit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# https://bandit.readthedocs.io/en/latest/config.html

tests:
- B108
- B306
- B307
- B313
- B314
- B315
- B316
- B317
- B318
- B319
- B320
- B325
- B602
- B604
9 changes: 9 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Test component setup."""
from homeassistant.setup import async_setup_component

from custom_components.pjlink2.const import DOMAIN


async def test_async_setup(hass):
"""Test the component gets setup."""
assert await async_setup_component(hass, DOMAIN, {}) is True

0 comments on commit 0328d26

Please sign in to comment.