Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.

Commit abfd138

Browse files
feat: Noise Sensors (#84)
Co-authored-by: andrii-balitskyi <[email protected]>
1 parent d3ce611 commit abfd138

File tree

5 files changed

+226
-25
lines changed

5 files changed

+226
-25
lines changed

seamapi/noise_sensors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from seamapi.types import AbstractNoiseSensors, AbstractNoiseThresholds

seamapi/noise_thresholds.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
from seamapi.types import (
2+
NoiseThreshold,
3+
AbstractNoiseThresholds,
4+
AbstractSeam as Seam,
5+
ActionAttempt,
6+
ActionAttemptError,
7+
)
8+
from typing import List, Optional
9+
import requests
10+
11+
from seamapi.utils.report_error import report_error
12+
13+
14+
class NoiseThresholds(AbstractNoiseThresholds):
15+
"""
16+
A class to interact with noise thresholds through the Seam API
17+
18+
...
19+
20+
Attributes
21+
----------
22+
seam : Seam
23+
Initial seam class
24+
25+
Methods
26+
-------
27+
list(device_id)
28+
Gets a list of noise thresholds of a noise-monitoring device
29+
create(device_id, starts_daily_at, ends_daily_at, sync=None, noise_threshold_decibels=None, noise_threshold_nrs=None)
30+
Creates a noise threshold on a noise-monitoring device
31+
"""
32+
33+
seam: Seam
34+
35+
def __init__(self, seam: Seam):
36+
"""
37+
Parameters
38+
----------
39+
seam : Seam
40+
Intial seam class
41+
"""
42+
self.seam = seam
43+
44+
@report_error
45+
def list(
46+
self,
47+
device_id: str,
48+
) -> List[NoiseThreshold]:
49+
"""Gets a list of noise thresholds.
50+
51+
Parameters
52+
----------
53+
device_id : str
54+
Device ID of a device to list noise thresholds of
55+
56+
Raises
57+
------
58+
Exception
59+
If the API request wasn't successful.
60+
61+
Returns
62+
------
63+
A list of noise thresholds.
64+
"""
65+
res = self.seam.make_request(
66+
"GET",
67+
"/noise_sensors/noise_thresholds/list",
68+
params={"device_id": device_id},
69+
)
70+
71+
return res["noise_thresholds"]
72+
73+
@report_error
74+
def create(
75+
self,
76+
device_id: str,
77+
starts_daily_at: str,
78+
ends_daily_at: str,
79+
sync: Optional[bool] = None,
80+
noise_threshold_decibels: Optional[float] = None,
81+
noise_threshold_nrs: Optional[float] = None,
82+
) -> List[NoiseThreshold]:
83+
"""Creates a noise threshold.
84+
85+
Parameters
86+
----------
87+
device_id : str
88+
Device ID of a device to list noise thresholds of
89+
sync: Optional[bool]
90+
Should wait for action attempt to resolve
91+
starts_daily_at: str,
92+
Time when noise threshold becomes active
93+
ends_daily_at: str,
94+
Time when noise threshold becomes inactive
95+
noise_threshold_decibels: Optional[float],
96+
The noise level in decibels
97+
noise_threshold_nrs: Optional[float],
98+
Noise Level in Noiseaware Noise Risk Score (NRS)
99+
Raises
100+
------
101+
Exception
102+
If the API request wasn't successful.
103+
104+
Returns
105+
------
106+
ActionAttempt
107+
"""
108+
params = {
109+
"device_id": device_id,
110+
"starts_daily_at": starts_daily_at,
111+
"ends_daily_at": ends_daily_at,
112+
}
113+
114+
arguments = {
115+
"sync": sync,
116+
"noise_threshold_decibels": noise_threshold_decibels,
117+
"noise_threshold_nrs": noise_threshold_nrs,
118+
}
119+
120+
for name in arguments:
121+
if arguments[name]:
122+
params.update({name: arguments[name]})
123+
124+
res = self.seam.make_request(
125+
"POST",
126+
"/noise_sensors/noise_thresholds/create",
127+
params={"device_id": device_id},
128+
)
129+
130+
json_aa = res["action_attempt"]
131+
error = None
132+
if "error" in json_aa and json_aa["error"] is not None:
133+
error = ActionAttemptError(
134+
type=json_aa["error"]["type"],
135+
message=json_aa["error"]["message"],
136+
)
137+
138+
return ActionAttempt(
139+
action_attempt_id=json_aa["action_attempt_id"],
140+
status=json_aa["status"],
141+
action_type=json_aa["action_type"],
142+
result=json_aa["result"],
143+
error=error,
144+
)
145+
146+
@report_error
147+
def delete(self, noise_threshold_id):
148+
raise NotImplementedError()
149+
150+
@report_error
151+
def update(self, noise_threshold_id):
152+
raise NotImplementedError()

seamapi/routes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .locks import Locks
88
from .access_codes import AccessCodes
99
from .action_attempts import ActionAttempts
10+
from .noise_thresholds import NoiseThresholds
1011

1112
class Routes(AbstractRoutes):
1213
def __init__(self):
@@ -18,6 +19,7 @@ def __init__(self):
1819
self.locks = Locks(seam=self)
1920
self.access_codes = AccessCodes(seam=self)
2021
self.action_attempts = ActionAttempts(seam=self)
22+
self.noise_thresholds = NoiseThresholds(seam=self)
2123

2224
def make_request(self):
2325
raise NotImplementedError()

seamapi/types.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,12 @@ def __init__(
4646
f'Action Attempt for "{action_type}" Failed. {error_type}: {error_message} (action_attempt_id={action_attempt_id})'
4747
)
4848

49+
4950
class WaitForAccessCodeFailedException(Exception):
50-
def __init__(
51-
self,
52-
message: str,
53-
access_code_id: str,
54-
errors: Optional[list] = []
55-
):
51+
def __init__(self, message: str, access_code_id: str, errors: Optional[list] = []):
5652
self.access_code_id = access_code_id
5753
self.errors = errors
58-
super().__init__(
59-
f'Failed while waiting for access code. ${message}'
60-
)
54+
super().__init__(f"Failed while waiting for access code. ${message}")
6155

6256

6357
@dataclass
@@ -92,15 +86,9 @@ class UnmanagedDevice:
9286
@dataclass
9387
class Event:
9488
event_id: str
95-
noiseaware_id: str
96-
activity_zone_id: str
9789
event_class: Union[str, None]
9890
event_type: Union[str, None]
99-
duration: Union[int, None]
100-
local_end_time: Union[str, None]
101-
local_start_time: Union[str, None]
102-
start_time: Union[str, None]
103-
end_time: Union[str, None]
91+
device_id: Optional[str]
10492
created_at: Union[str, None]
10593

10694

@@ -161,6 +149,7 @@ class ConnectedAccount:
161149
errors: List[str]
162150
custom_metadata: Dict[str, Union[str, int, bool, None]]
163151

152+
164153
@dataclass_json
165154
@dataclass
166155
class AccessCode:
@@ -174,6 +163,21 @@ class AccessCode:
174163
common_code_key: Optional[str] = None
175164

176165

166+
@dataclass_json
167+
@dataclass
168+
class NoiseThreshold:
169+
noise_threshold_id: str
170+
name: str
171+
noise_threshold_decibels: float
172+
173+
starts_daily_at: Optional[str]
174+
ends_daily_at: Optional[str]
175+
176+
noise_threshold_nrs: Optional[float]
177+
178+
created_at: str
179+
180+
177181
class AbstractActionAttempts(abc.ABC):
178182
@abc.abstractmethod
179183
def get(
@@ -265,6 +269,41 @@ def delete(
265269
raise NotImplementedError
266270

267271

272+
class AbstractNoiseThresholds(abc.ABC):
273+
@abc.abstractmethod
274+
def create(
275+
self,
276+
name: str,
277+
noise_threshold_decibels: Optional[float] = None,
278+
noise_threshold_nrs: Optional[float] = None,
279+
) -> NoiseThreshold:
280+
raise NotImplementedError
281+
282+
@abc.abstractmethod
283+
def update(
284+
self,
285+
noise_threshold_id: str,
286+
name: Optional[str] = None,
287+
noise_threshold_decibels: Optional[str] = None,
288+
noise_threshold_nrs: Optional[str] = None,
289+
) -> None:
290+
raise NotImplementedError
291+
292+
@abc.abstractmethod
293+
def list(self) -> List[NoiseThreshold]:
294+
raise NotImplementedError
295+
296+
@abc.abstractmethod
297+
def delete(self, noise_threshold_id: str) -> None:
298+
raise NotImplementedError
299+
300+
301+
class AbstractNoiseSensors(abc.ABC):
302+
@abc.abstractmethod
303+
def list_noise_levels(self, starting_after=None, ending_before=None) -> None:
304+
raise NotImplementedError
305+
306+
268307
class AbstractDevices(abc.ABC):
269308
@abc.abstractmethod
270309
def list(self) -> List[Device]:
@@ -345,6 +384,7 @@ class AbstractRoutes(abc.ABC):
345384
devices: AbstractDevices
346385
access_codes: AbstractAccessCodes
347386
action_attempts: AbstractActionAttempts
387+
noise_thresholds: AbstractNoiseThresholds
348388

349389
@abc.abstractmethod
350390
def make_request(self, method: str, path: str, **kwargs) -> Any:

tests/conftest.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,20 @@ def seam_backend():
3737
container_host = "host.docker.internal"
3838
db_url = db_url.replace(db_host, container_host)
3939
db_host = container_host
40+
elif db_host == "localhost":
41+
container_host = "172.17.0.1" # linux equivalent of host.docker.internal
42+
db_url = db_url.replace(db_host, container_host)
43+
db_host = container_host
4044

4145
with DockerContainer(
42-
os.environ.get(
43-
"SEAM_CONNECT_IMAGE", "ghcr.io/seamapi/seam-connect"
44-
)
45-
).with_env(
46-
"DATABASE_URL", db_url
47-
).with_env(
46+
os.environ.get("SEAM_CONNECT_IMAGE", "ghcr.io/seamapi/seam-connect")
47+
).with_env("DATABASE_URL", db_url).with_env(
4848
"POSTGRES_DATABASE", "postgres"
4949
).with_env(
5050
"NODE_ENV", "test"
5151
).with_env(
52-
"POSTGRES_HOST", db_host,
52+
"POSTGRES_HOST",
53+
db_host,
5354
).with_env(
5455
"SERVER_BASE_URL", "http://localhost:3020"
5556
).with_env(
@@ -77,6 +78,7 @@ def seam(seam_backend: Any, dotenv_fixture: Any):
7778
seam.make_request("POST", "/workspaces/reset_sandbox")
7879
yield seam
7980

81+
8082
@pytest.fixture
8183
def fake_sentry(monkeypatch):
8284
sentry_dsn = "https://[email protected]/123"
@@ -86,9 +88,11 @@ def fake_sentry(monkeypatch):
8688
sentry_init_args = {}
8789
sentry_capture_exception_calls = []
8890
sentry_add_breadcrumb_calls = []
91+
8992
class TestSentryClient(object):
9093
def __init__(self, *args, **kwargs):
9194
sentry_init_args.update(kwargs)
95+
9296
def set_context(self, *args, **kwargs):
9397
pass
9498

@@ -100,9 +104,11 @@ def set_context(self, *args, **kwargs):
100104

101105
class TestSentryHub(object):
102106
def __init__(self, *args, **kwargs):
103-
self.scope = TestSentryScope()
107+
self.scope = TestSentryScope()
108+
104109
def capture_exception(self, *args, **kwargs):
105-
sentry_capture_exception_calls.append((args, kwargs))
110+
sentry_capture_exception_calls.append((args, kwargs))
111+
106112
def add_breadcrumb(self, *args, **kwargs):
107113
sentry_add_breadcrumb_calls.append((args, kwargs))
108114

0 commit comments

Comments
 (0)