Skip to content

Commit

Permalink
Fixing issue with reporting incorrect state when out of battery (#360)
Browse files Browse the repository at this point in the history
* Refactoring converter to pass additional data for state conversion.

* Modifying stopped to always result as faulted.

* Changing behavior to fault when stopped in garden.

* Improving fault hits for stopped in garden.

* Adding fault detection when battery has been depleted.

* Adding logging configuration support.

* Cleanup.
  • Loading branch information
jeff-winn committed Aug 11, 2024
1 parent e0c736a commit 6f33993
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 51 deletions.
30 changes: 30 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@
]
}
},
"logger_type": {
"title": "Logging",
"type": "string",
"description": "This determines how your plugin logs statements. The default logger doesn't do debugging.",
"required": true,
"default": "default",
"enum": [
"default",
"imitation",
"force_debug"
],
"x-schema-form": {
"type": "select",
"titleMap": [
{
"value": "default",
"name": "Default - Homebridge stock logger"
},
{
"value": "imitation",
"name": "Imitation - Looks like the default logger but includes debugging support"
},
{
"value": "force_debug",
"name": "Force debug statements"
}
]
}
},
"username": {
"title": "Email Address",
"type": "string",
Expand Down Expand Up @@ -187,6 +216,7 @@
},
"device_type",
"sensor_mode",
"logger_type",
{
"type": "fieldset",
"title": "Account Settings",
Expand Down
33 changes: 22 additions & 11 deletions src/clients/automower/automowerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@ import { FetchClient, Response } from '../fetchClient';
export interface Mower {
type: string;
id: string;
attributes: {
system: Device;
battery: Battery;
mower: MowerState;
calendar: Calendar;
planner: Planner;
metadata: MowerMetadata;
positions: Position[];
settings: Settings;
statistics: Statistics;
};
attributes: MowerAttributes;
}

/**
* Describes the mower attributes.
*/
export interface MowerAttributes extends StatusAttributes {
system: Device;
calendar: Calendar;
positions: Position[];
settings: Settings;
statistics: Statistics;
}

/**
* Describes status attributes.
*/
export interface StatusAttributes {
battery: Battery;
mower: MowerState;
planner: Planner;
metadata: MowerMetadata;
}

/**
Expand Down
9 changes: 2 additions & 7 deletions src/clients/automower/automowerEventStreamClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PlatformLogger } from '../../diagnostics/platformLogger';
import { AccessToken } from '../../model';
import { WebSocketWrapper } from '../../primitives/webSocketWrapper';
import { AbstractEventStreamClient, EventStreamClient } from '../eventStreamClient';
import { Battery, Calendar, Headlight, MowerMetadata, MowerState, Planner, Position } from './automowerClient';
import { Calendar, Headlight, Position, StatusAttributes } from './automowerClient';

/**
* Describes a connected event.
Expand Down Expand Up @@ -34,12 +34,7 @@ export enum AutomowerEventTypes {
* Describes a status event.
*/
export interface StatusEvent extends AutomowerEvent {
attributes: {
battery: Battery;
mower: MowerState;
planner: Planner;
metadata: MowerMetadata;
};
attributes: StatusAttributes;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class AutomowerEventStreamService extends AbstractEventStreamService<Auto
connection: {
connected: event.attributes.metadata.connected
},
mower: this.stateConverter.convertMowerState(event.attributes.mower)
mower: this.stateConverter.convertStatusAttributes(event.attributes)
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as model from '../../../../model';

import { Activity, Mode, Mower, MowerState, State } from '../../../../clients/automower/automowerClient';
import { Activity, Mode, Mower, MowerState, State, StatusAttributes } from '../../../../clients/automower/automowerClient';
import { PlatformLogger } from '../../../../diagnostics/platformLogger';

/**
Expand All @@ -14,23 +14,23 @@ export interface AutomowerMowerStateConverter {
convertMower(mower: Mower): model.MowerState;

/**
* Converts the mower state.
* @param mower The mower state to convert.
* Converts the status attributes.
* @param attributes The mower attributes to convert.
*/
convertMowerState(mower: MowerState): model.MowerState;
convertStatusAttributes(attributes: StatusAttributes): model.MowerState;
}

export class AutomowerMowerStateConverterImpl implements AutomowerMowerStateConverter {
public constructor(private log: PlatformLogger) { }

public convertMower(mower: Mower): model.MowerState {
return this.convertMowerState(mower.attributes.mower);
return this.convertStatusAttributes(mower.attributes);
}

public convertMowerState(mower: MowerState): model.MowerState {
public convertStatusAttributes(attributes: StatusAttributes): model.MowerState {
return {
activity: this.convertActivity(mower),
state: this.convertState(mower)
activity: this.convertActivity(attributes.mower),
state: this.convertState(attributes)
};
}

Expand All @@ -39,40 +39,44 @@ export class AutomowerMowerStateConverterImpl implements AutomowerMowerStateConv
return model.Activity.OFF;
}

if (mower.mode === Mode.HOME || (mower.mode === Mode.MAIN_AREA && mower.state === State.RESTRICTED)) {
if (mower.mode === Mode.HOME || (mower.activity === Activity.PARKED_IN_CS)) {
return model.Activity.PARKED;
}

if (mower.mode === Mode.MAIN_AREA || mower.mode === Mode.SECONDARY_AREA) {
return model.Activity.MOWING;
}

this.log.debug('VALUE_NOT_SUPPORTED', mower.activity);
this.log.debug('VALUE_NOT_SUPPORTED', mower.mode);
return model.Activity.UNKNOWN;
}

protected convertState(mower: MowerState): model.State {
if (mower.state === State.STOPPED && mower.errorCode !== 0) {
return model.State.TAMPERED;
protected convertState(attributes: StatusAttributes): model.State {
if (attributes.battery.batteryPercent === 0) {
return model.State.FAULTED; // The battery has been depleted, and will need intervention to fix.
}

if (mower.activity === Activity.CHARGING) {
if (attributes.mower.mode === Mode.SECONDARY_AREA && attributes.mower.activity === Activity.STOPPED_IN_GARDEN) {
return model.State.FAULTED;
}

if (attributes.mower.activity === Activity.CHARGING) {
return model.State.CHARGING;
}

if (mower.activity === Activity.PARKED_IN_CS) {
if (attributes.mower.activity === Activity.PARKED_IN_CS) {
return model.State.IDLE;
}

if (mower.activity === Activity.GOING_HOME) {
if (attributes.mower.activity === Activity.GOING_HOME) {
return model.State.GOING_HOME;
}

if (mower.activity === Activity.LEAVING) {
if (attributes.mower.activity === Activity.LEAVING) {
return model.State.LEAVING_HOME;
}

switch (mower.state) {
switch (attributes.mower.state) {
case State.IN_OPERATION:
return model.State.IN_OPERATION;

Expand All @@ -93,7 +97,7 @@ export class AutomowerMowerStateConverterImpl implements AutomowerMowerStateConv
return model.State.UNKNOWN;

default:
this.log.debug('VALUE_NOT_SUPPORTED', mower.state);
this.log.debug('VALUE_NOT_SUPPORTED', attributes.mower.state);
return model.State.UNKNOWN;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ describe('AutomowerEventStreamService', () => {
}
};

stateConverter.setup(o => o.convertMowerState(mowerState)).returns({
stateConverter.setup(o => o.convertStatusAttributes(event.attributes)).returns({
activity: model.Activity.MOWING,
state: model.State.IN_OPERATION
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,78 @@ describe('AutomowerMowerStateConverterImpl', () => {
target = new AutomowerMowerStateConverterImpl(log.object());
});

it('should return faulted when battery has been depleted while mowing main area', () => {
const mower: StatusEvent = {
id: '12345',
type: AutomowerEventTypes.STATUS,
attributes:{
battery:{
batteryPercent: 0
},
mower:{
mode: Mode.MAIN_AREA,
activity: Activity.NOT_APPLICABLE,
state: State.PAUSED,
errorCode: 0,
errorCodeTimestamp: 0
},
planner:{
nextStartTimestamp: 0,
override:{
action: OverrideAction.NOT_ACTIVE
},
restrictedReason: RestrictedReason.NOT_APPLICABLE
},
metadata:{
connected: true,
statusTimestamp: 1723315112510
}
}
};

const result = target.convertStatusAttributes(mower.attributes);

expect(result).toBeDefined();
expect(result.activity).toEqual(model.Activity.MOWING);
expect(result.state).toEqual(model.State.FAULTED);
});

it('should return faulted when battery has been depleted while mowing secondary area', () => {
const mower: StatusEvent = {
id: '12345',
type: AutomowerEventTypes.STATUS,
attributes:{
battery:{
batteryPercent: 0
},
mower:{
mode: Mode.SECONDARY_AREA,
activity: Activity.NOT_APPLICABLE,
state: State.PAUSED,
errorCode: 0,
errorCodeTimestamp: 0
},
planner:{
nextStartTimestamp: 0,
override:{
action: OverrideAction.NOT_ACTIVE
},
restrictedReason: RestrictedReason.NOT_APPLICABLE
},
metadata:{
connected: true,
statusTimestamp: 1723315112510
}
}
};

const result = target.convertStatusAttributes(mower.attributes);

expect(result).toBeDefined();
expect(result.activity).toEqual(model.Activity.MOWING);
expect(result.state).toEqual(model.State.FAULTED);
});

it('should return parked and idle when parked and outside of scheduled window', () => {
const mower: StatusEvent = {
id: '12345',
Expand All @@ -41,11 +113,12 @@ describe('AutomowerMowerStateConverterImpl', () => {
},
metadata:{
connected: true,
statusTimestamp: 1723071500034}
statusTimestamp: 1723071500034
}
};
}
};

const result = target.convertMowerState(mower.attributes.mower);
const result = target.convertStatusAttributes(mower.attributes);

expect(result).toBeDefined();
expect(result.activity).toEqual(model.Activity.PARKED);
Expand Down Expand Up @@ -616,7 +689,7 @@ describe('AutomowerMowerStateConverterImpl', () => {
expect(result.state).toEqual(model.State.LEAVING_HOME);
});

it('should return mowing when stopped in garden', () => {
it('should return faulted when stopped in garden', () => {
const mower: Mower = {
id: '12345',
type: 'mower',
Expand All @@ -629,7 +702,7 @@ describe('AutomowerMowerStateConverterImpl', () => {
activity: Activity.STOPPED_IN_GARDEN,
errorCode: 0,
errorCodeTimestamp: 0,
mode: Mode.MAIN_AREA,
mode: Mode.SECONDARY_AREA,
state: State.UNKNOWN
},
planner: {
Expand Down Expand Up @@ -682,7 +755,7 @@ describe('AutomowerMowerStateConverterImpl', () => {

expect(result).toBeDefined();
expect(result.activity).toEqual(model.Activity.MOWING);
expect(result.state).toEqual(model.State.UNKNOWN);
expect(result.state).toEqual(model.State.FAULTED);
});

it('should return mowing when mowing', () => {
Expand Down Expand Up @@ -825,7 +898,7 @@ describe('AutomowerMowerStateConverterImpl', () => {
log.verify(o => o.debug(It.IsAny(), It.IsAny()), Times.Once());
});

it('should return tampered when stopped with error', () => {
it('should return faulted when stopped with error', () => {
const mower: Mower = {
id: '12345',
type: 'mower',
Expand Down Expand Up @@ -890,10 +963,10 @@ describe('AutomowerMowerStateConverterImpl', () => {
const result = target.convertMower(mower);

expect(result).toBeDefined();
expect(result.state).toEqual(model.State.TAMPERED);
expect(result.state).toEqual(model.State.FAULTED);
});

it('should return charging while mowing secondary area', () => {
it('should return charging while mowing secondary area and battery has been depleted', () => {
const mower: Mower = {
id: '12345',
type: 'mower',
Expand Down Expand Up @@ -935,7 +1008,7 @@ describe('AutomowerMowerStateConverterImpl', () => {
serialNumber: 1
},
battery: {
batteryPercent: 100
batteryPercent: 80
},
calendar: {
tasks: [
Expand All @@ -962,7 +1035,7 @@ describe('AutomowerMowerStateConverterImpl', () => {
expect(result.state).toEqual(model.State.CHARGING);
});

it('should return charging while mowing main area', () => {
it('should return charging while mowing main area and battery has been depleted', () => {
const mower: Mower = {
id: '12345',
type: 'mower',
Expand Down Expand Up @@ -1004,7 +1077,7 @@ describe('AutomowerMowerStateConverterImpl', () => {
serialNumber: 1
},
battery: {
batteryPercent: 100
batteryPercent: 80
},
calendar: {
tasks: [
Expand Down

0 comments on commit 6f33993

Please sign in to comment.