Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Setting the Manual Feed Amount for Air Smart Feeders #63

Merged
merged 23 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8378848
Adding a Number-based "Manual Feed" Entity so a defined number of fee…
Nistp Dec 1, 2024
f12cbaf
Merge branch 'isssue33' of https://github.com/Nistp/petlibro into iss…
Nistp Dec 1, 2024
d5cd788
Merge branch 'jjjonesjr33:dev' into isssue33
Nistp Jan 5, 2025
8fd95bd
Created static manual feed quantity, a setter, and a number gauge for…
Nistp Jan 12, 2025
a502a50
Merge branch 'jjjonesjr33:dev' into isssue33
Nistp Jan 12, 2025
5a5ceae
Was able to dispense more than 1/24th cup of food
Nistp Jan 13, 2025
20c9c8c
Merge branch 'isssue33' of https://github.com/Nistp/petlibro into iss…
Nistp Jan 13, 2025
130cf6e
Catch-up on updates, merge and resolve upstream nodes
Nistp Jan 19, 2025
28fd0e5
Implemented the option to set the manual feed dispense value via a n…
Nistp Jan 20, 2025
6396dff
Merge branch 'dev' into isssue33
jjjonesjr33 Jan 21, 2025
6143a03
Merge branch 'dev' into isssue33
jjjonesjr33 Jan 25, 2025
37844af
Updated api.py
jjjonesjr33 Jan 25, 2025
147a672
Updated button.py
jjjonesjr33 Jan 25, 2025
e0a37f6
Updated api.py
jjjonesjr33 Jan 25, 2025
6bdc8f2
Updated air_smart_feeder.py
jjjonesjr33 Jan 25, 2025
240c290
Updated granary_smart_camera_feeder.py
jjjonesjr33 Jan 25, 2025
e13dca8
Updated granary_smart_feeder.py
jjjonesjr33 Jan 25, 2025
345c26c
Updated space_smart_feeder.py
jjjonesjr33 Jan 25, 2025
18f7a1d
Updated one_rfid_smart_feeder.py
jjjonesjr33 Jan 25, 2025
c763cbd
Updated number.py
jjjonesjr33 Jan 25, 2025
103f659
Updated button.py
jjjonesjr33 Jan 25, 2025
ce5336e
Updated button.py
jjjonesjr33 Jan 25, 2025
507c6dd
Updated button.py
jjjonesjr33 Jan 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions custom_components/petlibro/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,18 +483,18 @@ async def set_lid_mode(self, serial: str, value: str):
_LOGGER.error(f"Failed to set lid mode for device {serial}: {e}")
raise

async def set_manual_feed(self, serial: str) -> JSON:

async def set_manual_feed(self, serial: str, feed_value=1) -> JSON: # Provide a default argument for the feed value just in case this works differently with other feeders
"""Trigger manual feeding for a specific device."""
_LOGGER.debug(f"Triggering manual feeding for device with serial: {serial}")

try:
# Generate a dynamic request ID for the manual feeding
request_id = str(uuid.uuid4()).replace("-", "")

# Send the POST request to trigger manual feeding
response = await self.session.post("/device/device/manualFeeding", json={
"deviceSn": serial,
"grainNum": 1, # Number of grains dispensed
"grainNum": int(feed_value), # Number of grains dispensed, make sure it's an integer and not a float
"requestId": request_id # Use dynamic request ID
})

Expand Down
32 changes: 28 additions & 4 deletions custom_components/petlibro/devices/feeders/air_smart_feeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@

class AirSmartFeeder(Device): # Inherit directly from Device
def __init__(self, *args, **kwargs):
"""Initialize the feeder with default values."""
super().__init__(*args, **kwargs)
# Set the conversion mode explicitly for this feeder type
self.conversion_mode = "1/24" # Static definition for AirSmartFeeder
self._manual_feed_quantity = None # Default to None initially

async def refresh(self):
"""Refresh the device data from the API."""
try:
await super().refresh() # Call the refresh method from Device

# Fetch specific data for this device
grain_status = await self.api.device_grain_status(self.serial)
real_info = await self.api.device_real_info(self.serial)
attribute_settings = await self.api.device_attribute_settings(self.serial)

# Update internal data with fetched API data
self.update_data({
"grainStatus": grain_status or {},
"realInfo": real_info or {}
"realInfo": real_info or {},
"getAttributeSetting": attribute_settings or {}
})
except PetLibroAPIError as err:
_LOGGER.error(f"Error refreshing data for AirSmartFeeder: {err}")
Expand Down Expand Up @@ -167,6 +170,13 @@ def screen_display_switch(self) -> bool:
def remaining_desiccant(self) -> str:
"""Get the remaining desiccant days."""
return cast(str, self._data.get("remainingDesiccantDays", "unknown"))

@property
def manual_feed_quantity(self):
if self._manual_feed_quantity is None:
_LOGGER.warning(f"manual_feed_quantity is None for {self.serial}, setting default to 1.")
self._manual_feed_quantity = 1 # Default value
return self._manual_feed_quantity

# Error-handling updated for set_feeding_plan
async def set_feeding_plan(self, value: bool) -> None:
Expand Down Expand Up @@ -227,13 +237,27 @@ async def set_sound_switch(self, value: bool) -> None:
except aiohttp.ClientError as err:
_LOGGER.error(f"Failed to set sound switch for {self.serial}: {err}")
raise PetLibroAPIError(f"Error setting sound switch: {err}")

@manual_feed_quantity.setter
def manual_feed_quantity(self, value: float):
"""Set the manual feed quantity."""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self._manual_feed_quantity = value

async def set_manual_feed_quantity(self, value: float):
"""Set the manual feed quantity with a default value handling"""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self.manual_feed_quantity = max(1, min(value, 24)) # Ensure value is within valid range
await self.refresh()

# Method for manual feeding
async def set_manual_feed(self) -> None:
_LOGGER.debug(f"Triggering manual feed for {self.serial}")
try:
await self.api.set_manual_feed(self.serial)
feed_quantity = getattr(self, "manual_feed_quantity", 1) # Default to 1 if not set
await self.api.set_manual_feed(self.serial, feed_quantity)
await self.refresh() # Refresh the state after the action
except aiohttp.ClientError as err:
_LOGGER.error(f"Failed to trigger manual feed for {self.serial}: {err}")
raise PetLibroAPIError(f"Error triggering manual feed: {err}")

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
_LOGGER = getLogger(__name__)

class GranarySmartCameraFeeder(Device): # Inherit directly from Device
def __init__(self, *args, **kwargs):
"""Initialize the feeder with default values."""
super().__init__(*args, **kwargs)
self._manual_feed_quantity = None # Default to None initially

async def refresh(self):
"""Refresh the device data from the API."""
try:
Expand Down Expand Up @@ -188,6 +193,13 @@ def remaining_desiccant(self) -> str:
"""Get the remaining desiccant days."""
return cast(str, self._data.get("remainingDesiccantDays", "unknown"))

@property
def manual_feed_quantity(self):
if self._manual_feed_quantity is None:
_LOGGER.warning(f"manual_feed_quantity is None for {self.serial}, setting default to 1.")
self._manual_feed_quantity = 1 # Default value
return self._manual_feed_quantity

# Error-handling updated for set_feeding_plan
async def set_feeding_plan(self, value: bool) -> None:
_LOGGER.debug(f"Setting feeding plan to {value} for {self.serial}")
Expand Down Expand Up @@ -248,11 +260,24 @@ async def set_sound_switch(self, value: bool) -> None:
_LOGGER.error(f"Failed to set sound switch for {self.serial}: {err}")
raise PetLibroAPIError(f"Error setting sound switch: {err}")

@manual_feed_quantity.setter
def manual_feed_quantity(self, value: float):
"""Set the manual feed quantity."""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self._manual_feed_quantity = value

async def set_manual_feed_quantity(self, value: float):
"""Set the manual feed quantity with a default value handling"""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self.manual_feed_quantity = max(1, min(value, 12)) # Ensure value is within valid range
await self.refresh()

# Method for manual feeding
async def set_manual_feed(self) -> None:
_LOGGER.debug(f"Triggering manual feed for {self.serial}")
try:
await self.api.set_manual_feed(self.serial)
feed_quantity = getattr(self, "manual_feed_quantity", 1) # Default to 1 if not set
await self.api.set_manual_feed(self.serial, feed_quantity)
await self.refresh() # Refresh the state after the action
except aiohttp.ClientError as err:
_LOGGER.error(f"Failed to trigger manual feed for {self.serial}: {err}")
Expand Down
29 changes: 27 additions & 2 deletions custom_components/petlibro/devices/feeders/granary_smart_feeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
_LOGGER = getLogger(__name__)

class GranarySmartFeeder(Device): # Inherit directly from Device
def __init__(self, *args, **kwargs):
"""Initialize the feeder with default values."""
super().__init__(*args, **kwargs)
self._manual_feed_quantity = None # Default to None initially

async def refresh(self):
"""Refresh the device data from the API."""
try:
Expand Down Expand Up @@ -163,6 +168,13 @@ def remaining_desiccant(self) -> str:
"""Get the remaining desiccant days."""
return cast(str, self._data.get("remainingDesiccantDays", "unknown"))

@property
def manual_feed_quantity(self):
if self._manual_feed_quantity is None:
_LOGGER.warning(f"manual_feed_quantity is None for {self.serial}, setting default to 1.")
self._manual_feed_quantity = 1 # Default value
return self._manual_feed_quantity

# Error-handling updated for set_feeding_plan
async def set_feeding_plan(self, value: bool) -> None:
_LOGGER.debug(f"Setting feeding plan to {value} for {self.serial}")
Expand Down Expand Up @@ -223,16 +235,29 @@ async def set_sound_switch(self, value: bool) -> None:
_LOGGER.error(f"Failed to set sound switch for {self.serial}: {err}")
raise PetLibroAPIError(f"Error setting sound switch: {err}")

@manual_feed_quantity.setter
def manual_feed_quantity(self, value: float):
"""Set the manual feed quantity."""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self._manual_feed_quantity = value

async def set_manual_feed_quantity(self, value: float):
"""Set the manual feed quantity with a default value handling"""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self.manual_feed_quantity = max(1, min(value, 12)) # Ensure value is within valid range
await self.refresh()

# Method for manual feeding
async def set_manual_feed(self) -> None:
_LOGGER.debug(f"Triggering manual feed for {self.serial}")
try:
await self.api.set_manual_feed(self.serial)
feed_quantity = getattr(self, "manual_feed_quantity", 1) # Default to 1 if not set
await self.api.set_manual_feed(self.serial, feed_quantity)
await self.refresh() # Refresh the state after the action
except aiohttp.ClientError as err:
_LOGGER.error(f"Failed to trigger manual feed for {self.serial}: {err}")
raise PetLibroAPIError(f"Error triggering manual feed: {err}")

# Method for setting the feeding plan
async def set_feeding_plan(self, value: bool) -> None:
_LOGGER.debug(f"Setting feeding plan to {value} for {self.serial}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
_LOGGER = getLogger(__name__)

class OneRFIDSmartFeeder(Device):
def __init__(self, *args, **kwargs):
"""Initialize the feeder with default values."""
super().__init__(*args, **kwargs)
self._manual_feed_quantity = None # Default to None initially

async def refresh(self):
"""Refresh the device data from the API."""
try:
Expand Down Expand Up @@ -190,6 +195,13 @@ def remaining_desiccant(self) -> str:
@property
def desiccant_frequency(self) -> float:
return self._data.get("realInfo", {}).get("changeDesiccantFrequency", 0)

@property
def manual_feed_quantity(self):
if self._manual_feed_quantity is None:
_LOGGER.warning(f"manual_feed_quantity is None for {self.serial}, setting default to 1.")
self._manual_feed_quantity = 1 # Default value
return self._manual_feed_quantity

async def set_desiccant_frequency(self, value: float) -> None:
_LOGGER.debug(f"Setting desiccant frequency to {value} for {self.serial}")
Expand Down Expand Up @@ -275,11 +287,24 @@ async def set_sound_switch(self, value: bool) -> None:
_LOGGER.error(f"Failed to set sound switch for {self.serial}: {err}")
raise PetLibroAPIError(f"Error setting sound switch: {err}")

@manual_feed_quantity.setter
def manual_feed_quantity(self, value: float):
"""Set the manual feed quantity."""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self._manual_feed_quantity = value

async def set_manual_feed_quantity(self, value: float):
"""Set the manual feed quantity with a default value handling"""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self.manual_feed_quantity = max(1, min(value, 12)) # Ensure value is within valid range
await self.refresh()

# Method for manual feeding
async def set_manual_feed(self) -> None:
_LOGGER.debug(f"Triggering manual feed for {self.serial}")
try:
await self.api.set_manual_feed(self.serial)
feed_quantity = getattr(self, "manual_feed_quantity", 1) # Default to 1 if not set
await self.api.set_manual_feed(self.serial, feed_quantity)
await self.refresh() # Refresh the state after the action
except aiohttp.ClientError as err:
_LOGGER.error(f"Failed to trigger manual feed for {self.serial}: {err}")
Expand Down
27 changes: 26 additions & 1 deletion custom_components/petlibro/devices/feeders/space_smart_feeder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
_LOGGER = getLogger(__name__)

class SpaceSmartFeeder(Device): # Inherit directly from Device
def __init__(self, *args, **kwargs):
"""Initialize the feeder with default values."""
super().__init__(*args, **kwargs)
self._manual_feed_quantity = None # Default to None initially

async def refresh(self):
"""Refresh the device data from the API."""
try:
Expand Down Expand Up @@ -163,6 +168,13 @@ def remaining_desiccant(self) -> str:
"""Get the remaining desiccant days."""
return cast(str, self._data.get("remainingDesiccantDays", "unknown"))

@property
def manual_feed_quantity(self):
if self._manual_feed_quantity is None:
_LOGGER.warning(f"manual_feed_quantity is None for {self.serial}, setting default to 1.")
self._manual_feed_quantity = 1 # Default value
return self._manual_feed_quantity

# Error-handling updated for set_feeding_plan
async def set_feeding_plan(self, value: bool) -> None:
_LOGGER.debug(f"Setting feeding plan to {value} for {self.serial}")
Expand Down Expand Up @@ -223,11 +235,24 @@ async def set_sound_switch(self, value: bool) -> None:
_LOGGER.error(f"Failed to set sound switch for {self.serial}: {err}")
raise PetLibroAPIError(f"Error setting sound switch: {err}")

@manual_feed_quantity.setter
def manual_feed_quantity(self, value: float):
"""Set the manual feed quantity."""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self._manual_feed_quantity = value

async def set_manual_feed_quantity(self, value: float):
"""Set the manual feed quantity with a default value handling"""
_LOGGER.debug(f"Setting manual feed quantity: serial={self.serial}, value={value}")
self.manual_feed_quantity = max(1, min(value, 12)) # Ensure value is within valid range
await self.refresh()

# Method for manual feeding
async def set_manual_feed(self) -> None:
_LOGGER.debug(f"Triggering manual feed for {self.serial}")
try:
await self.api.set_manual_feed(self.serial)
feed_quantity = getattr(self, "manual_feed_quantity", 1) # Default to 1 if not set
await self.api.set_manual_feed(self.serial, feed_quantity)
await self.refresh() # Refresh the state after the action
except aiohttp.ClientError as err:
_LOGGER.error(f"Failed to trigger manual feed for {self.serial}: {err}")
Expand Down
58 changes: 56 additions & 2 deletions custom_components/petlibro/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,43 @@ async def async_set_native_value(self, value: float) -> None:
Feeder: [
],
AirSmartFeeder: [
PetLibroNumberEntityDescription[AirSmartFeeder](
key ="manual_feed_quantity",
translation_key ="manual_feed_quantity",
native_unit_of_measurement = " / 24 cups",
native_max_value = 24,
native_min_value = 1,
native_step = 1,
value = lambda device: device.manual_feed_quantity,
method = lambda device, value: device.set_manual_feed_quantity(value),
name = "Manual Feed Quantity"
),
],
GranarySmartFeeder: [
PetLibroNumberEntityDescription[GranarySmartFeeder](
key ="manual_feed_quantity",
translation_key ="manual_feed_quantity",
native_unit_of_measurement = " / 12 cups",
native_max_value = 12,
native_min_value = 1,
native_step = 1,
value = lambda device: device.manual_feed_quantity,
method = lambda device, value: device.set_manual_feed_quantity(value),
name = "Manual Feed Quantity"
),
],
GranarySmartCameraFeeder: [
PetLibroNumberEntityDescription[GranarySmartCameraFeeder](
key ="manual_feed_quantity",
translation_key ="manual_feed_quantity",
native_unit_of_measurement = " / 12 cups",
native_max_value = 12,
native_min_value = 1,
native_step = 1,
value = lambda device: device.manual_feed_quantity,
method = lambda device, value: device.set_manual_feed_quantity(value),
name = "Manual Feed Quantity"
),
],
OneRFIDSmartFeeder: [
PetLibroNumberEntityDescription[OneRFIDSmartFeeder](
Expand Down Expand Up @@ -124,11 +157,33 @@ async def async_set_native_value(self, value: float) -> None:
value=lambda device: device.lid_close_time,
method=lambda device, value: device.set_lid_close_time(value),
name="Lid Close Time"
)
),
PetLibroNumberEntityDescription[OneRFIDSmartFeeder](
key="manual_feed_quantity",
translation_key="manual_feed_quantity",
native_unit_of_measurement=" / 12 cups",
native_max_value=12,
native_min_value=1,
native_step=1,
value=lambda device: getattr(device, "manual_feed_quantity", 1), # Default to 1 if not set
method=lambda device, value: device.set_manual_feed_quantity(value),
name="Manual Feed Quantity"
),
],
PolarWetFoodFeeder: [
],
SpaceSmartFeeder: [
PetLibroNumberEntityDescription[SpaceSmartFeeder](
key ="manual_feed_quantity",
translation_key ="manual_feed_quantity",
native_unit_of_measurement = " / 12 cups",
native_max_value = 12,
native_min_value = 1,
native_step = 1,
value = lambda device: device.manual_feed_quantity,
method = lambda device, value: device.set_manual_feed_quantity(value),
name = "Manual Feed Quantity"
),
],
DockstreamSmartFountain: [
],
Expand Down Expand Up @@ -179,4 +234,3 @@ async def async_setup_entry(

# Add number entities to Home Assistant
async_add_entities(entities)

Loading