Skip to content

Commit 5e3a801

Browse files
authored
Merge branch 'dev' into fix/upstream-234
2 parents b94e30c + c78c69e commit 5e3a801

18 files changed

Lines changed: 716 additions & 23 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ logger:
133133
---
134134
## Star History
135135

136-
[![Star History Chart](https://api.star-history.com/svg?repos=jjjonesjr33/petlibro&type=Date&theme=dark)](https://www.star-history.com/#jjjonesjr33/petlibro&Date)
136+
[![Star History Chart](https://api.star-history.com/chart?repos=jjjonesjr33/petlibro&type=date&legend=top-left&theme=dark&sealed_token=hpOo4LKHDwWaJig-07vmXGM_vv_VOZXCYWNiyL62EViJNtExbKzCNjvwS5x7ZT8Q1LCpRTMrR5vKelTRpBuDjc1_5N-XNwjaHOOOm7FpFWa02M55X_fyrle5peHkqvXZEWdEUwrr5RCbfZTrnOVEo7Y9weU0jZv0L3wIi0ybyvKcxKL477nTaBzFpJdA)](https://www.star-history.com/?repos=jjjonesjr33%2Fpetlibro&type=date&legend=top-left)
137137

138138
[stars]: https://github.com/jjjonesjr33/petlibro/stargazers
139139
[starsbadge]: https://img.shields.io/github/stars/jjjonesjr33/petlibro?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE2IDE2Ij48cGF0aCBzdHlsZT0iZmlsbDojY2NjY2NjIiBkPSJNOCAuMjVhLjc1Ljc1IDAgMCAxIC42NzMuNDE4bDEuODgyIDMuODE1IDQuMjEuNjEyYS43NS43NSAwIDAgMSAuNDE2IDEuMjc5bC0zLjA0NiAyLjk3LjcxOSA0LjE5MmEuNzUxLjc1MSAwIDAgMS0xLjA4OC43OTFMOCAxMi4zNDdsLTMuNzY2IDEuOThhLjc1Ljc1IDAgMCAxLTEuMDg4LS43OWwuNzItNC4xOTRMLjgxOCA2LjM3NGEuNzUuNzUgMCAwIDEgLjQxNi0xLjI4bDQuMjEtLjYxMUw3LjMyNy42NjhBLjc1Ljc1IDAgMCAxIDggLjI1Wm0wIDIuNDQ1TDYuNjE1IDUuNWEuNzUuNzUgMCAwIDEtLjU2NC40MWwtMy4wOTcuNDUgMi4yNCAyLjE4NGEuNzUuNzUgMCAwIDEgLjIxNi42NjRsLS41MjggMy4wODQgMi43NjktMS40NTZhLjc1Ljc1IDAgMCAxIC42OTggMGwyLjc3IDEuNDU2LS41My0zLjA4NGEuNzUuNzUgMCAwIDEgLjIxNi0uNjY0bDIuMjQtMi4xODMtMy4wOTYtLjQ1YS43NS43NSAwIDAgMS0uNTY0LS40MUw4IDIuNjk0WiI+PC9wYXRoPjwvc3ZnPg==&label=Stars&color=ffffff

custom_components/petlibro/binary_sensor.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,14 @@ def extra_state_attributes(self):
230230
value_fn=lambda device: device.feeding_plan_state,
231231
name="Feeding Schedule"
232232
),
233+
PetLibroBinarySensorEntityDescription[AirSmartFeeder](
234+
key="power_connected",
235+
translation_key="power_connected",
236+
icon="mdi:power-plug",
237+
device_class=BinarySensorDeviceClass.PLUG,
238+
should_report=lambda device: device.power_connected is not None,
239+
name="Power Connected"
240+
),
233241
],
234242
GranarySmartFeeder: [
235243
PetLibroBinarySensorEntityDescription[GranarySmartFeeder](
@@ -294,6 +302,22 @@ def extra_state_attributes(self):
294302
value_fn=lambda device: device.feeding_plan_state,
295303
name="Feeding Schedule"
296304
),
305+
PetLibroBinarySensorEntityDescription[GranarySmartFeeder](
306+
key="left_food_low",
307+
translation_key="left_food_low",
308+
icon="mdi:bowl-mix-outline",
309+
device_class=BinarySensorDeviceClass.PROBLEM,
310+
should_report=lambda device: device.left_food_low is not None,
311+
name="Left Food Status"
312+
),
313+
PetLibroBinarySensorEntityDescription[GranarySmartFeeder](
314+
key="right_food_low",
315+
translation_key="right_food_low",
316+
icon="mdi:bowl-mix-outline",
317+
device_class=BinarySensorDeviceClass.PROBLEM,
318+
should_report=lambda device: device.right_food_low is not None,
319+
name="Right Food Status"
320+
),
297321
],
298322
GranarySmartCameraFeeder: [
299323
PetLibroBinarySensorEntityDescription[GranarySmartCameraFeeder](
@@ -358,6 +382,22 @@ def extra_state_attributes(self):
358382
value_fn=lambda device: device.feeding_plan_state,
359383
name="Feeding Schedule"
360384
),
385+
PetLibroBinarySensorEntityDescription[GranarySmartCameraFeeder](
386+
key="motion_detected",
387+
translation_key="motion_detected",
388+
icon="mdi:motion-sensor",
389+
device_class=BinarySensorDeviceClass.MOTION,
390+
should_report=lambda device: device.motion_detected is not None,
391+
name="Motion Detected"
392+
),
393+
PetLibroBinarySensorEntityDescription[GranarySmartCameraFeeder](
394+
key="sound_detected",
395+
translation_key="sound_detected",
396+
icon="mdi:ear-hearing",
397+
device_class=BinarySensorDeviceClass.SOUND,
398+
should_report=lambda device: device.sound_detected is not None,
399+
name="Sound Detected"
400+
),
361401
],
362402
OneRFIDSmartFeeder: [
363403
PetLibroBinarySensorEntityDescription[OneRFIDSmartFeeder](
@@ -453,6 +493,14 @@ def extra_state_attributes(self):
453493
value_fn=lambda device: device.feeding_plan_state,
454494
name="Feeding Schedule"
455495
),
496+
PetLibroBinarySensorEntityDescription[OneRFIDSmartFeeder](
497+
key="rotor_stuck",
498+
translation_key="rotor_stuck",
499+
icon="mdi:alert",
500+
device_class=BinarySensorDeviceClass.PROBLEM,
501+
should_report=lambda device: device.rotor_stuck is not None,
502+
name="Rotor Status"
503+
),
456504
],
457505
PolarWetFoodFeeder: [
458506
PetLibroBinarySensorEntityDescription[PolarWetFoodFeeder](
@@ -613,6 +661,14 @@ def extra_state_attributes(self):
613661
should_report=lambda device: device.light_switch is not None,
614662
name="Indicator"
615663
),
664+
PetLibroBinarySensorEntityDescription[DockstreamSmartFountain](
665+
key="weight_calibration_error",
666+
translation_key="weight_calibration_error",
667+
icon="mdi:alert",
668+
device_class=BinarySensorDeviceClass.PROBLEM,
669+
should_report=lambda device: device.weight_calibration_error is not None,
670+
name="Weight Calibration Error"
671+
),
616672
],
617673
DockstreamSmartRFIDFountain: [
618674
PetLibroBinarySensorEntityDescription[DockstreamSmartRFIDFountain](

custom_components/petlibro/button.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,14 @@ class PetLibroButtonEntityDescription(ButtonEntityDescription, PetLibroEntityDes
657657
translation_key="filter_reset",
658658
set_fn=lambda device: device.set_filter_reset(),
659659
name="Filter Reset"
660-
)
660+
),
661+
PetLibroButtonEntityDescription[DockstreamSmartFountain](
662+
key="calibrate_weight",
663+
translation_key="calibrate_weight",
664+
icon="mdi:scale",
665+
set_fn=lambda device: device.calibrate_weight(),
666+
name="Calibrate Weight Sensor"
667+
),
661668
],
662669
DockstreamSmartRFIDFountain: [
663670
PetLibroButtonEntityDescription[DockstreamSmartRFIDFountain](

custom_components/petlibro/date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async def async_setup_entry(
5353

5454
# Ensure that the pets are loaded
5555
if not (pets := hub.pets):
56-
_LOGGER.warning("No pets found in hub during date setup.")
56+
_LOGGER.debug("No pets found in hub during date setup.")
5757

5858
if not (pets): # or devices
5959
return

custom_components/petlibro/devices/feeders/air_smart_feeder.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ async def refresh(self):
3434
get_work_record = await self.api.get_device_work_record(self.serial)
3535
feeding_plan_list = (await self.api.device_feeding_plan_list(self.serial)
3636
if self._data.get("enableFeedingPlan") else [])
37+
data_real_info = await self.api.device_data_real_info(self.serial)
3738

38-
# Update internal data with fetched API data
3939
self.update_data({
4040
"grainStatus": grain_status or {},
4141
"realInfo": real_info or {},
42+
"dataRealInfo": data_real_info or {},
4243
"getUpgrade": get_upgrade or {},
4344
"getAttributeSetting": attribute_settings or {},
4445
"getfeedingplantoday": get_feeding_plan_today or {},
@@ -150,6 +151,13 @@ def electric_quantity(self) -> float:
150151
quantity = self._data.get("realInfo", {}).get("electricQuantity")
151152
return quantity if isinstance(quantity, (float, int)) else 0
152153

154+
@property
155+
def power_connected(self) -> bool | None:
156+
power_type = self._data.get("dataRealInfo", {}).get("powerType")
157+
if power_type is None:
158+
return None
159+
return power_type == 3
160+
153161
@property
154162
def enable_feeding_plan(self) -> bool:
155163
return self._data.get("realInfo", {}).get("enableFeedingPlan", False)

custom_components/petlibro/devices/feeders/granary_smart_camera_feeder.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ async def refresh(self):
3131
get_work_record = await self.api.get_device_work_record(self.serial)
3232
feeding_plan_list = (await self.api.device_feeding_plan_list(self.serial)
3333
if self._data.get("enableFeedingPlan") else [])
34+
get_device_events = await self.api.device_events(self.serial)
3435
# Update internal data with fetched API data
3536
self.update_data({
3637
"grainStatus": grain_status or {},
@@ -39,7 +40,8 @@ async def refresh(self):
3940
"getUpgrade": get_upgrade or {},
4041
"getfeedingplantoday": get_feeding_plan_today or {},
4142
"feedingPlan": feeding_plan_list or [],
42-
"workRecord": get_work_record or [],
43+
"workRecord": get_work_record or [],
44+
"getDeviceEvents": get_device_events or {},
4345
})
4446
except PetLibroAPIError as err:
4547
_LOGGER.error(f"Error refreshing data for GranarySmartCameraFeeder: {err}")
@@ -216,6 +218,16 @@ def video_record_switch(self) -> bool:
216218
def video_record_mode(self) -> str:
217219
"""Return the current video recording mode."""
218220
return self._data.get("realInfo", {}).get("videoRecordMode", "unknown")
221+
222+
@property
223+
def motion_detected(self) -> bool:
224+
events = self._data.get("getDeviceEvents", {}).get("data", {}).get("eventInfos", [])
225+
return any(event.get("eventKey") == "MOTION_DETECTED" for event in events)
226+
227+
@property
228+
def sound_detected(self) -> bool:
229+
events = self._data.get("getDeviceEvents", {}).get("data", {}).get("eventInfos", [])
230+
return any(event.get("eventKey") == "SOUND_DETECTED" for event in events)
219231

220232
@property
221233
def remaining_desiccant(self) -> float | None:

custom_components/petlibro/devices/feeders/granary_smart_feeder.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,34 @@ def battery_state(self) -> str:
8686
def food_dispenser_state(self) -> bool:
8787
return not bool(self._data.get("realInfo", {}).get("grainOutletState", True))
8888

89+
@property
90+
def bowl_mode(self) -> str:
91+
return self._data.get("realInfo", {}).get("bowlMode", "SINGLE_BOWL")
92+
8993
@property
9094
def food_low(self) -> bool:
91-
return not bool(self._data.get("realInfo", {}).get("surplusGrain", True))
95+
surplus = self._data.get("realInfo", {}).get("surplusGrain")
96+
if surplus is not None:
97+
return not bool(surplus)
98+
left = self._data.get("realInfo", {}).get("leftWarehouseSurplusGrain")
99+
right = self._data.get("realInfo", {}).get("rightWarehouseSurplusGrain")
100+
if left is not None and right is not None:
101+
return not bool(left) or not bool(right)
102+
return True
103+
104+
@property
105+
def left_food_low(self) -> bool | None:
106+
value = self._data.get("realInfo", {}).get("leftWarehouseSurplusGrain")
107+
if value is None:
108+
return None
109+
return not bool(value)
110+
111+
@property
112+
def right_food_low(self) -> bool | None:
113+
value = self._data.get("realInfo", {}).get("rightWarehouseSurplusGrain")
114+
if value is None:
115+
return None
116+
return not bool(value)
92117

93118
@property
94119
def unit_type(self) -> int:

custom_components/petlibro/devices/feeders/one_rfid_smart_feeder.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ async def refresh(self):
3434
get_feeding_plan_today = await self.api.device_feeding_plan_today_new(self.serial)
3535
feeding_plan_list = (await self.api.device_feeding_plan_list(self.serial)
3636
if self._data.get("enableFeedingPlan") else [])
37+
data_real_info = await self.api.device_data_real_info(self.serial)
3738
# Update internal data with fetched API data
3839
self.update_data({
3940
"grainStatus": grain_status or {},
4041
"realInfo": real_info or {},
42+
"dataRealInfo": data_real_info or {},
4143
"getUpgrade": get_upgrade or {},
4244
"getAttributeSetting": attribute_settings or {},
4345
"getDefaultMatrix": get_default_matrix or {},
@@ -128,6 +130,13 @@ def online(self) -> bool:
128130
def running_state(self) -> bool:
129131
return self._data.get("realInfo", {}).get("runningState", "IDLE") == "RUNNING"
130132

133+
@property
134+
def rotor_stuck(self) -> bool | None:
135+
exception = self._data.get("dataRealInfo", {}).get("exceptionMessage")
136+
if exception is None:
137+
return None
138+
return "rotor" in exception.lower() or "stuck" in exception.lower()
139+
131140
@property
132141
def whether_in_sleep_mode(self) -> bool:
133142
return bool(self._data.get("getAttributeSetting", {}).get("enableSleepMode", False))

custom_components/petlibro/devices/feeders/polar_wet_food_feeder.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ async def refresh(self):
3434
"wetFeedingPlan": wet_feeding_plan or {},
3535
"getfeedingplantoday": get_feeding_plan_today or {}
3636
})
37+
if self.manual_feed_id is not None:
38+
self._manual_feed_active = True
3739
except PetLibroAPIError as err:
3840
_LOGGER.error(f"Error refreshing data for PolarWetFoodFeeder: {err}")
3941

@@ -74,8 +76,13 @@ def electric_quantity(self) -> float:
7476

7577
@property
7678
def feeding_plan_state(self) -> bool:
77-
"""Return the state of the feeding plan."""
78-
return bool(self._data.get("enableFeedingPlan", False))
79+
"""Return the state of the feeding plan from wetFeedingPlan data."""
80+
wet_plan = self._data.get("wetFeedingPlan", {})
81+
# API does not provide an explicit enabled flag for wet plans; non-empty plan = enabled
82+
if not wet_plan or not wet_plan.get("templateName"):
83+
return False
84+
plan_entries = wet_plan.get("plan", [])
85+
return bool(plan_entries)
7986

8087
@property
8188
def food_low(self) -> bool:
@@ -117,6 +124,10 @@ def next_feeding_end_time(self) -> str:
117124
except ValueError:
118125
return "Invalid time"
119126

127+
def __init__(self, *args, **kwargs):
128+
super().__init__(*args, **kwargs)
129+
self._manual_feed_active = False
130+
120131
@property
121132
def manual_feed_id(self) -> int:
122133
"""Returns the manual feed ID."""
@@ -125,7 +136,7 @@ def manual_feed_id(self) -> int:
125136
@property
126137
def manual_feed_now(self) -> bool:
127138
"""Returns whether the feeder is set to feed now or not."""
128-
return self.manual_feed_id is not None
139+
return self._manual_feed_active
129140

130141
@property
131142
def online(self) -> bool:
@@ -187,9 +198,13 @@ async def set_manual_feed_now(self, start: bool, plate: int) -> None:
187198
await self.api.set_manual_feed_now(self.serial, plate)
188199
else:
189200
_LOGGER.debug(f"Triggering stop feed now for {self.serial}")
190-
await self.api.set_stop_feed_now(self.serial, self.manual_feed_id)
201+
if self.manual_feed_id is None:
202+
_LOGGER.warning("No manual feed ID available to stop for %s", self.serial)
203+
else:
204+
await self.api.set_stop_feed_now(self.serial, self.manual_feed_id)
191205

192-
await self.refresh() # Refresh the state after the action
206+
self._manual_feed_active = start
207+
await self.refresh()
193208
except aiohttp.ClientError as err:
194209
_LOGGER.error(f"Failed to trigger manual feed now for {self.serial} with plate no.{plate}: {err}")
195210
raise PetLibroAPIError(f"Error triggering manual feed now: {err}")

custom_components/petlibro/devices/fountains/dockstream_smart_fountain.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from ..device import Device
77
from typing import cast
88
from logging import getLogger
9-
109
_LOGGER = getLogger(__name__)
1110

1211
class DockstreamSmartFountain(Device):
@@ -356,3 +355,19 @@ def update_progress(self) -> float:
356355

357356
progress = upgrade_data.get("progress")
358357
return float(progress) if progress is not None else 0.0
358+
359+
@property
360+
def weight_calibration_error(self) -> bool | None:
361+
exception = self._data.get("dataRealInfo", {}).get("exceptionMessage")
362+
if exception is None:
363+
return None
364+
return "calibration" in exception.lower() or "weight" in exception.lower()
365+
366+
async def calibrate_weight(self) -> None:
367+
_LOGGER.debug(f"Calibrating weight sensor for {self.serial}")
368+
try:
369+
await self.api.exec_device_command(self.serial, "CALIBRATE")
370+
await self.refresh()
371+
except (aiohttp.ClientError, PetLibroAPIError) as err:
372+
_LOGGER.error(f"Failed to calibrate weight sensor for {self.serial}: {err}")
373+
raise PetLibroAPIError(f"Error calibrating weight sensor: {err}")

0 commit comments

Comments
 (0)