Skip to content

Commit

Permalink
feat(vendor.cecotec): add support for cecotec conga
Browse files Browse the repository at this point in the history
  • Loading branch information
adrigzr committed Jan 28, 2022
1 parent dde8f2e commit e6091e5
Show file tree
Hide file tree
Showing 38 changed files with 2,794 additions and 585 deletions.
691 changes: 691 additions & 0 deletions backend/lib/robots/cecotec/CecotecCongaRobot.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const BasicControlCapability = require("../../../core/capabilities/BasicControlCapability");

/**
* @extends BasicControlCapability<import("../CecotecCongaRobot")>
*/
module.exports = class CecotecBasicControlCapability extends BasicControlCapability {
async start() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.start();
}

async stop() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.stop();
}

async pause() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.pause();
}

async home() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.home();
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const CarpetModeControlCapability = require("../../../core/capabilities/CarpetModeControlCapability");

/**
* @extends CarpetModeControlCapability<import("../CecotecCongaRobot")>
*/
class CecotecCarpetModeControlCapability extends CarpetModeControlCapability {
/**
* This function polls the current carpet mode state and stores the attributes in our robostate
*
* @abstract
* @returns {Promise<boolean>}
*/
async isEnabled() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

return this.robot.robot.device.config?.isCarpetModeEnabled || false;
}

/**
* @abstract
* @returns {Promise<void>}
*/
async enable() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.setCarpetMode(true);
}

/**
* @abstract
* @returns {Promise<void>}
*/
async disable() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.setCarpetMode(false);
}
}

module.exports = CecotecCarpetModeControlCapability;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @typedef {import("../../../entities/core/ValetudoVirtualRestrictions")} ValetudoVirtualRestrictions
*/

const CombinedVirtualRestrictionsCapability = require("../../../core/capabilities/CombinedVirtualRestrictionsCapability");
const {Pixel} = require("@agnoc/core");

/**
* @extends CombinedVirtualRestrictionsCapability<import("../CecotecCongaRobot")>
*/
class CecotecCombinedVirtualRestrictionsCapability extends CombinedVirtualRestrictionsCapability {
/**
* @param {ValetudoVirtualRestrictions} virtualRestrictions
* @returns {Promise<void>}
*/
async setVirtualRestrictions({ virtualWalls, restrictedZones }) {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

const map = this.robot.robot.device.map;

if (!map) {
return;
}

const offset = map.size.y;
const areas = [
...virtualWalls.map(({ points }) => {
return [
map.toCoordinate(new Pixel({
x: points.pA.x,
y: offset - points.pA.y,
})),
map.toCoordinate(new Pixel({
x: points.pB.x,
y: offset - points.pB.y,
})),
map.toCoordinate(new Pixel({
x: points.pA.x,
y: offset - points.pA.y,
})),
map.toCoordinate(new Pixel({
x: points.pB.x,
y: offset - points.pB.y,
})),
];
}),
...restrictedZones.map(({ points }) => {
return [
map.toCoordinate(new Pixel({
x: points.pA.x,
y: offset - points.pA.y,
})),
map.toCoordinate(new Pixel({
x: points.pD.x,
y: offset - points.pD.y,
})),
map.toCoordinate(new Pixel({
x: points.pC.x,
y: offset - points.pC.y,
})),
map.toCoordinate(new Pixel({
x: points.pB.x,
y: offset - points.pB.y,
})),
];
})
];

await this.robot.robot.setRestrictedZones(areas);
await this.robot.robot.updateMap();
}
}

module.exports = CecotecCombinedVirtualRestrictionsCapability;
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const ConsumableMonitoringCapability = require("../../../core/capabilities/ConsumableMonitoringCapability");
const ConsumableStateAttribute = require("../../../entities/state/attributes/ConsumableStateAttribute");
const {CONSUMABLE_TYPE} = require("@agnoc/core");

const { TYPE, SUB_TYPE, UNITS } = ConsumableStateAttribute;
const MAIN_BRUSH_LIFE_TIME = 320 * 60;
const SIDE_BRUSH_LIFE_TIME = 220 * 60;
const FILTER_LIFE_TIME = 160 * 60;
const DISHCLOTH_LIFE_TIME = 100 * 60;

/**
* @extends ConsumableMonitoringCapability<import("../CecotecCongaRobot")>
*/
module.exports = class CecotecConsumableMonitoringCapability extends ConsumableMonitoringCapability {
async getConsumables() {
if (!this.robot.robot) {
return [];
}

const list = await this.robot.robot.getConsumables();
const consumables = list.map(({ type, used }) => {
switch (type) {
case CONSUMABLE_TYPE.MAIN_BRUSH:
return new ConsumableStateAttribute({
type: TYPE.BRUSH,
subType: SUB_TYPE.MAIN,
remaining: {
unit: UNITS.MINUTES,
value: Math.max(MAIN_BRUSH_LIFE_TIME - used, 0)
}
});
case CONSUMABLE_TYPE.SIDE_BRUSH:
return new ConsumableStateAttribute({
type: TYPE.BRUSH,
subType: SUB_TYPE.SIDE_RIGHT,
remaining: {
unit: UNITS.MINUTES,
value: Math.max(SIDE_BRUSH_LIFE_TIME - used, 0)
}
});
case CONSUMABLE_TYPE.FILTER:
return new ConsumableStateAttribute({
type: TYPE.FILTER,
subType: SUB_TYPE.MAIN,
remaining: {
unit: UNITS.MINUTES,
value: Math.max(FILTER_LIFE_TIME - used, 0)
}
});
case CONSUMABLE_TYPE.DISHCLOTH:
return new ConsumableStateAttribute({
type: TYPE.MOP,
subType: SUB_TYPE.MAIN,
remaining: {
unit: UNITS.MINUTES,
value: Math.max(DISHCLOTH_LIFE_TIME - used, 0)
}
});
}
});

consumables.forEach(c => {
return this.robot.state.upsertFirstMatchingAttribute(c);
});

// @ts-ignore
this.robot.emitStateUpdated();

return consumables;
}

/**
* @param {string} type
* @param {string} [subType]
* @returns {Promise<void>}
*/
async resetConsumable(type, subType) {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

/**
* @type {import("@agnoc/core").ConsumableType}
*/
let consumable;

if (type === TYPE.BRUSH && subType === SUB_TYPE.MAIN) {
consumable = CONSUMABLE_TYPE.MAIN_BRUSH;
} else if (type === TYPE.BRUSH && subType === SUB_TYPE.SIDE_RIGHT) {
consumable = CONSUMABLE_TYPE.SIDE_BRUSH;
} else if (type === TYPE.FILTER) {
consumable = CONSUMABLE_TYPE.FILTER;
} else if (type === TYPE.MOP) {
consumable = CONSUMABLE_TYPE.DISHCLOTH;
}

if (consumable) {
await this.robot.robot.resetConsumable(consumable);
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const CurrentStatisticsCapability = require("../../../core/capabilities/CurrentStatisticsCapability");
const ValetudoDataPoint = require("../../../entities/core/ValetudoDataPoint");

/**
* @extends CurrentStatisticsCapability<import("../CecotecCongaRobot")>
*/
class CecotecCurrentStatisticsCapability extends CurrentStatisticsCapability {
/**
* @return {Promise<Array<ValetudoDataPoint>>}
*/
async getStatistics() {
return [
new ValetudoDataPoint({
type: ValetudoDataPoint.TYPES.TIME,
value: (this.robot.robot?.device.currentClean?.time || 0) * 60,
}),
new ValetudoDataPoint({
type: ValetudoDataPoint.TYPES.AREA,
value: (this.robot.robot?.device.currentClean?.size || 0) * 100,
}),
];
}

getProperties() {
return {
availableStatistics: [
ValetudoDataPoint.TYPES.TIME,
ValetudoDataPoint.TYPES.AREA,
],
};
}
}

module.exports = CecotecCurrentStatisticsCapability;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const DoNotDisturbCapability = require("../../../core/capabilities/DoNotDisturbCapability");
const ValetudoDNDConfiguration = require("../../../entities/core/ValetudoDNDConfiguration");
const {DeviceQuietHours, DeviceTime} = require("@agnoc/core");

/**
* @extends DoNotDisturbCapability<import("../CecotecCongaRobot")>
*/
module.exports = class CecotecDoNotDisturbCapability extends DoNotDisturbCapability {

/**
* @returns {Promise<import("../../../entities/core/ValetudoDNDConfiguration")>}
*/
async getDndConfiguration() {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

const quietHours = await this.robot.robot.getQuietHours();

return new ValetudoDNDConfiguration({
enabled: quietHours.isEnabled,
start: {
hour: quietHours.begin.hour,
minute: quietHours.begin.minute
},
end: {
hour: quietHours.end.hour,
minute: quietHours.end.minute
}
});
}

/**
* @param {import("../../../entities/core/ValetudoDNDConfiguration")} dndConfig
* @returns {Promise<void>}
*/
async setDndConfiguration(dndConfig) {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

await this.robot.robot.setQuietHours(new DeviceQuietHours({
isEnabled: dndConfig.enabled,
begin: new DeviceTime({
hour: dndConfig.start.hour,
minute: dndConfig.start.minute,
}),
end: new DeviceTime({
hour: dndConfig.end.hour,
minute: dndConfig.end.minute,
}),
}));
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const FanSpeedControlCapability = require("../../../core/capabilities/FanSpeedControlCapability");
const {DeviceFanSpeed} = require("@agnoc/core");

/**
* @extends FanSpeedControlCapability<import("../CecotecCongaRobot")>
*/
module.exports = class CecotecFanSpeedControlCapability extends FanSpeedControlCapability {
/**
* @returns {Array<string>}
*/
getFanSpeedPresets() {
return this.presets.map(p => {
return p.name;
});
}

async selectPreset(preset) {
if (!this.robot.robot) {
throw new Error("There is no robot connected to server");
}

const matchedPreset = this.presets.find(p => {
return p.name === preset;
});

if (!matchedPreset) {
throw new Error("Invalid Preset");
}

await this.robot.robot.setFanSpeed(new DeviceFanSpeed({ value: matchedPreset.value }));
}
};
Loading

0 comments on commit e6091e5

Please sign in to comment.