This is another look at the states and event transitions that I described in an earlier project log. I'm now ready to implement it in a Home Assistant automation definition. It will actually be two automations, but the automation for the dryer is just a clone of the automation for the washer with the names of devices and entities adjusted.
As a reminder, there are 4 event inputs for each machine: door open, door closed, power on, and power off. Those are events, not states, so "power on" means "power is turned on". I'm configuring the automations as "queued", so the events can never happen simultaneously, no matter how it happens in real life. There are some impossible or nonsense transitions, and there are also some fairly simple considerations.
- If the power turns on, the machine must be transitioned to the RUNNING state, no matter what we thought its previous state was.
- If the power turns off, the machine must be transitioned to the STOPPED state, no matter what we thought its previous state was.
- If the door becomes open, the machine cannot be in the RUNNING state.
- The door cannot become open without being closed, and vice versa.
Current state | Door open | Door closed | Power on | Power off |
Unloaded | Loading | - | Running | Stopped |
Loading | - | Loaded | Running | Stopped |
Loaded | Loading | - | Running | Stopped |
Running | - | - | Running | Stopped |
Stopped | Unloading | - | Running | Stopped |
Unloading | - | Unloaded | Running | Stopped |
Unknown | - | - | Running | Stopped |
This leads to fairly simple logic. Power events don't need to consider anything else. Door events only need to consider the current state to decide what the next state should be. There are some cases where things are not perfect, but we can tolerate them. For example, when in the STOPPED state and the door is opened, we transition to the unloading state. It will be very common for the machine to be re-loaded without the door being closed in the meantime. When the door is closed, we will think we are in the UNLOADED state when we are actually in the LOADED state. In the normal case, the power will soon turn on and transition us to the RUNNING state. We don't care that we missed the LOADING and LOADED states, except that we don't necessarily have the opportunity to alert if the machine is loaded but not powered on for some amount of time. That's not much different from someone opening the door, not loading the machine, and then closing the door. We would need some additional sensors of some kind to distinguish those cases, and it's not worth it (to me).
For the automation, I am using just one automation per machine, and the trigger can be any of the four event types. Even though it might seem simpler to have separate automations for each of the possible triggers, or at least for the possible trigger types, I'm combining them all into a single automation so that I can configure it as "queued" and not have to worry about concurrency edge cases.
I mentioned in an earlier project log that I am using templated sensors for the door contact sensors. That avoids having to deal with unavailable or unknown values. I'm using the templated forms for the automation triggers. Incidentally, I had to be careful to use entity state changes as the triggers. I originally used the contact sensor device, but that triggers on any attribute change. The devices phone home every few hours even if the door does not open or close. I think that's to report their battery level as an attribute.
For the power triggers, I used a numeric value comparison. Power above a threshold value for a certain period of time is considered on. Power below that threshold for a certain period of time is considered off. At the moment, I'm using 8 watts for the threshold and 1.5 minutes for the time period. I may have to tweak those things later, depending on what I observe in real life.
The action in the automation is just changing the state of the applicable dropdown helper entity. It consists of a nested choose action that implements the transition table. The default action is to do nothing, so we only have to deal with the table cells that have entries. The outer choose action is based on which trigger invoked the automation. The action for the power triggers is constant. For the door triggers, each has an inner choose action the distinguishes the current state.
(Update: I later added a 1 minute delay to the contact sensors to avoid some noise. If someone opens the dryer door to see if it's got something in it, it can move the state from STOPPED to UNLOADING and then UNLOADED. This can matter for the case of automatically cancelling a notification if we get fooled into thinking the dryer was unloaded when it wasn't.)
That all look pretty complicated in YAML. Here's a partial look at the GUI version to give a flavor of it.
And here is (most of) the actions for the door closed trigger:
Here's the complete YAML form:
alias: State transitions Washer description: "" trigger: - platform: numeric_state id: power_on entity_id: sensor.washer_power above: 8 for: hours: 0 minutes: 1 seconds: 30 - platform: numeric_state id: power_off entity_id: sensor.washer_power below: 8 for: hours: 0 minutes: 1 seconds: 30 - platform: state entity_id: - binary_sensor.contact_sensor_washer_triggered to: "on" id: door_open - platform: state entity_id: - binary_sensor.contact_sensor_washer_triggered to: "off" id: door_closed condition: [] action: - choose: - conditions: - condition: trigger id: power_on sequence: - service: input_select.select_option data: option: Running target: entity_id: input_select.washer_state - conditions: - condition: trigger id: power_off sequence: - service: input_select.select_option data: option: Stopped target: entity_id: input_select.washer_state - conditions: - condition: trigger id: door_open sequence: - choose: - conditions: - condition: state entity_id: input_select.washer_state state: Unloaded sequence: - service: input_select.select_option data: option: Loading target: entity_id: input_select.washer_state - conditions: - condition: state entity_id: input_select.washer_state state: Loaded sequence: - service: input_select.select_option data: option: Loading target: entity_id: input_select.washer_state - conditions: - condition: state entity_id: input_select.washer_state state: Stopped sequence: - service: input_select.select_option data: option: Unloading target: entity_id: input_select.washer_state - conditions: - condition: trigger id: door_close sequence: - choose: - conditions: - condition: state entity_id: input_select.washer_state state: Loading sequence: - service: input_select.select_option data: option: Loaded target: entity_id: input_select.washer_state - conditions: - condition: state entity_id: input_select.washer_state state: Unloading sequence: - service: input_select.select_option data: option: Loaded target: entity_id: input_select.washer_state mode: queued max: 10
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.