Feature/Schedule-Control-Dry-Feeders - #209
Conversation
Feature/Schedule-Control
Fix Naming For Schedule Binary Sensor's
Fix Trailing Comma
Last Plan vs Schedule naming updates
|
@cristianchelu for your HA card: :) |
jjjonesjr33
left a comment
There was a problem hiding this comment.
Great work man! Let's give it a try!
|
Thanks for the heads up @C4-Dimitri ! I'll try to add this to the card soon -- I don't have a feeder to test with, though. Just two things from my perspective: The same schedules are now two binary sensors with 4 buttons to turn them off/on. Any reason why these 6 can't be converted to two |
To be clear, the 2 schedules provided are 1 for today correct, but this is because any interaction with this schedule only applies to the day, so skipping an event only Skips it for that day. Second is not a weekly schedule per se, its just the entire schedule of all plans that may exist for a feeder, whether they are set to repeat, was a one-off whatever. So you could create a one-off feed event that will show in this schedule forever, but it will NEVER repeat, unless you go and edit it to do so. All of the today's feed events show in the main schedule too, not just in the today's schedule. Not quite sure what you mean by merging them? I think it would be very confusing to not have clear view of which are today and which are just in the whole schedule.
You are correct that at least turning on/off the individual schedules COULD be a switch eliminating the binary_sensor, and the on/off buttons for each. |
Sorry, I should have added more detail. The # feeding_plan_state
plan_data.get(str(plan["planId"]), {}).get("label") or f"plan_{plan.get('index', plan['planId'])}": {
"time": plan.get("time"),
"amount (weight)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, weight, True)} {weight.symbol}",
"amount (volume)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, volume, True)} {volume.symbol}",
"state": {1: "Pending", 2: "Skipped", 3: "Completed", 4: "Skipped, Time Passed"}.get(plan.get("state"), "Unknown"),
"repeat": plan.get("repeat"),
"planID": plan.get("planId"),
}
# feeding_schedule
plan.get("label") or f"plan_{plan_id}": {
"planID": int(plan_id),
"time": plan.get("executionTime"),
"amount (weight)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, weight, True)} {weight.symbol}",
"amount (volume)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, volume, True)} {volume.symbol}",
"enabled": plan.get("enable", False),
"repeat_days": plan.get("repeatDay", "[]"),
"sound": plan.get("enableAudio", False),
}Right now, when setting up the card config, I would need a user to select type: custom:dispenser-schedule-card
device:
type: petlibro
plan_state_entity: binary_sensor.feeding_schedule # opportunity for user confusion
schedule_entity: binary_sensor.feeding_plan_state # config entities swapped
# etcIf on Does that make sense?
Understood. From the card POV it's the same UX issue of making the user supply more configuration for each on/off action. Maybe I can go through the deviceId and auto-discover related actions/entities, but I prefer simplicity when/if I can have it :) |
|
Ah, i understand now! what we can do is: plan.get("label") or f"plan_{plan_id}": {
"planID": int(plan_id),
"time": plan.get("executionTime"),
"amount (weight)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, weight, True)} {weight.symbol}",
"amount (volume)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, volume, True)} {volume.symbol}",
"enabled": plan.get("enable", False),
"repeat_days": plan.get("repeatDay", "[]"),
"sound": plan.get("enableAudio", False),
"state": {1: "Pending", 2: "Skipped", 3: "Completed", 4: "Skipped, Time Passed"}.get(plan.get("state"), "Unknown"),
}but crucially, this will only populate for events that are actually active in Todays schedule, so we should have a state for "Not in Today's Schedule:", What would be good to have as well, is just whether an event is enabled or disabled overall: plan.get("label") or f"plan_{plan_id}": {
"planID": int(plan_id),
"time": plan.get("executionTime"),
"amount (weight)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, weight, True)} {weight.symbol}",
"amount (volume)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, volume, True)} {volume.symbol}",
"enabled": plan.get("enable", False),
"repeat_days": plan.get("repeatDay", "[]"),
"sound": plan.get("enableAudio", False),
"feed_state": {1: "Pending", 2: "Skipped", 3: "Completed", 4: "Skipped, Time Passed"}.get(plan.get("state"), "Unknown"),
# Note, i will need to reference the daily feed schedule here instead. ⌃
"state": {false: "Disabled", true: "Enabled"}.get(plan.get("enable"), "Unknown"),
}is that what you mean? |
Yes, exactly. |
|
I can 100% do that. Ill see if I can get to it today :) |
"amount (weight)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, weight, True)} {weight.symbol}",
"amount (volume)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, volume, True)} {volume.symbol}",This means the actions expect raw "extra_attributes": {
"weight_unit": "g",
"volume_unit": "cups",
"conversion_factor": 2.5,
"plans": [
{ "planID": 123, "amount_weight": 25, "amount_volume": 25 } // grainNum: 10
]
}Or just P.S. just out of curiosity. Any reason for |
correct. if i were to add as for why its so in summary: plan.get("label") or f"plan_{plan_id}": {
"planID": int(plan_id),
"time": plan.get("executionTime"),
"amount (weight)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, weight, True)} {weight.symbol}",
"amount (volume)": f"{Unit.convert_feed(plan.get('grainNum', 0) * conv, None, volume, True)} {volume.symbol}",
"amount (raw)": plan.get('grainNum', 0)",
"enabled": plan.get("enable", False),
"repeat_days": plan.get("repeatDay", "[]"),
"sound": plan.get("enableAudio", False),
"feed_state": {1: "Pending", 2: "Skipped", 3: "Completed", 4: "Skipped, Time Passed"}.get(plan.get("state"), "Unknown"),
# Note, i will need to reference the daily feed schedule here instead. ⌃
}realised we already have whether the plan is enabled or disabled, as the "enabled" in the above, so a second state wasnt needed. |
From the card developer perspective, as long as I have the raw amount to plug back into the edit action/service, I'm fine. From a HA user perspective, my 2¢ say attributes don't take up space in the history DB, they don't crowd any dashboard or device page, and are otherwise invisible in normal HA usage. Having all easily available to use in templates / automations would make sense to me.
Having keys disappear breaking automations because of a settings change in a different app would be weird to me. |
|
@cristianchelu Whole schedule: Today's Schedule: let me know if anything you feel is still missing. |
|
@C4-Dimitri, As far as I can tell from the code, there's not also an action (service) to disable, skip, or delete a plan ID directly? Also, from what I understand, the skip and enable on the Petkit api side is 3 separate Could you expose Either that, or In the meantime I'll release a |
|
@cristianchelu, Ok, so you are correct, there is no service for disabling, skipping or deleting a plan ID, you are correct, that is done via the selection of a plan in the select, and pressing one of the relevant buttons. i can make services for this as well, no worries there, but the user will need to know what plan ID to put into the service (same as edit plan service that currently exists, so no issue there).
dont see any harm in making more services for each thing. lets the users have a way to do it from the device using the entities, but also do it via automation without interacting with the entities themselves. will make another pull request later today to add those services in. |
|
If you have time, I would suggest you wait for the -prerelease version of the card so you can test all functionality, so you can open a single PR addressing anything else we might catch. i.e. right now plan entries are spread between normal home-assistant attributes, I have to filter them out and trust that what remains are our plan keys ( /** Attribute keys that are not feeding plans. */
const RESERVED_ATTRIBUTE_KEYS = new Set([
"device_class",
"icon",
"friendly_name",
"unit_of_measurement",
"state_class",
"attribution",
"supported_features",
"assumed_state",
"restored",
]);I'm developing the device handler blindly (have no petlibro feeder) by having Opus double-check against your implementation, so I'm lost without real-world validation from your side :) I'm on the petlibro discord under the same username if you want to talk realtime. |
|
@cristianchelu , sounds good, just sent you a friend request on Discord :) |

Proposed change:
Closes #26, Closes #79, Closes #195, Closes #196
This PR adds full feeding plan management for all dry food feeders, including the ability to add, edit, enable, disable, skip, and delete individual scheduled feeding plans directly from Home Assistant.
Services
Two new services have been added:
petlibro.add_feeding_plan— Add a new scheduled feeding plan to a dry food feeder. Takes a device target, feed time, number of portions, an optional label, optional repeat days, and an optional meal call sound toggle.petlibro.edit_feeding_plan— Edit an existing scheduled feeding plan on a dry food feeder. Only the fields you provide will be changed — anything left blank keeps its current value. The Plan ID can be found in the feeder's Feeding Schedule binary sensor attributes OR in the new select entity, where it is named "Label - Plan ID".Select Entities
Two new select entities are added per dry feeder:
feeding_plan_select) — Lists all plans in the recurring schedule. Used to target a specific plan for the enable, disable, and delete buttons.feeding_plan_today_select) — Lists only today's scheduled feeds. Used to target a specific plan for the skip and un-skip buttons.Button Entities
Seven new button entities are added per dry feeder for direct control of the schedule:
Binary Sensor Entities
The existing
feeding_plan_stateandfeeding_schedulesensors have been converted from sensors to binary sensors (on= schedule globally enabled,off= disabled):feeding_plan_state) — State reflects whether the feeding schedule is globally enabled. Attributes contain today's scheduled feeds with formatted weight and volume amounts, and their current status (Pending / Skipped / Completed).feeding_schedule) — Same on/off state. Attributes contain the full recurring schedule with time, portions, repeat days, and sound settings.Entity Naming
As part of this PR, all entity friendly names that refer to the entire set of planned feeds have been updated to use the word "Schedule", while individual planned feed events are referred to as a "Plan". The rationale:
This distinction was necessary given the number of new controls — users now interact with both the schedule as a whole and individual plans within it, so clear naming avoids confusion.
Type of change:
Checklist:
Additional notes: