Skip to content

Commit

Permalink
Shelly MQTT driver
Browse files Browse the repository at this point in the history
- Scan network on activation
- Scan network on init till all minions has IPs
- Add battery status along with DeviceStatus interface
- MQTT driver refactor
- Add Shelly MQTT driver
- Remove IFTTT APIs
  • Loading branch information
haimkastner committed Aug 6, 2022
1 parent cf57d0f commit d2b55cf
Show file tree
Hide file tree
Showing 25 changed files with 548 additions and 587 deletions.
5 changes: 3 additions & 2 deletions backend/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"cSpell.words": [
"broadlink",
"Casanet",
"Tasmota",
"Tuya",
"Yeelight",
"broadlink"
"Yeelight"
]
}
2 changes: 1 addition & 1 deletion backend/api-errors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- { 7404 : 'there is not module handler for minion brand'}
- { 8404 : 'unknown minion model'}
- { 9404 : 'action not exist'}
- { 10404 : 'collection not exist'}

- { 1405 : 'incorrect minion status for current minion type'}
- { 2405 : 'incorrect minion status for activity minion type'}
Expand All @@ -33,7 +34,6 @@
- { 4409 : 'device already in max uses with other minion'}
- { 5409 : 'you have to select local server to connect to it'}
- { 6409 : 'the minion not support command recording or sending'}
- { 7409 : 'ifttt trigger fail'}

- { 1422 : 'wrong schema'}
- { 2422 : 'wrong schema, with wrong message'}
Expand Down
8 changes: 8 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,14 @@ class App {
return;
}

if(err.name === 'ValidateError') {
logger.error(
`Validation error, req: ${req.method} ${req.path} error: ${err.message} body: ${JSON.stringify(
req.body,
)} fields : ${JSON.stringify(err.fields)}`,
);
}

logger.error(
`express route crash, req: ${req.method} ${req.path} error: ${err.message} body: ${JSON.stringify(
req.body,
Expand Down
30 changes: 27 additions & 3 deletions backend/src/business-layer/devicesBl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { SyncEvent } from 'ts-events';
import { Duration } from 'unitsnet-js';
import { inspect } from 'util';
import { DevicesDal, DevicesDalSingleton } from '../data-layer/devicesDal';
import { DeviceKind, LocalNetworkDevice } from '../models/sharedInterfaces';
import { ModulesManager, ModulesManagerSingltone } from '../modules/modulesManager';
import { ModulesManager, modulesManager } from '../modules/modulesManager';
import { LocalNetworkReader } from '../utilities/lanManager';
import { logger } from '../utilities/logger';

export class DevicesBl {
const DETECT_NETWORK_CHANGES_ACTIVATION = Duration.FromHours(1);

export class DevicesService {
/**
* Local devices changes feed.
*/
Expand Down Expand Up @@ -33,6 +37,26 @@ export class DevicesBl {
this.devicesDal = devicesDal;
this.localNetworkReader = localNetworkReader;
this.modulesManager = modulesManager;

// Attach subscription to the drivers update regarding device status update. such as battery status etc.
this.modulesManager.deviceStatusChangedEvent.attach((deviceUpdate) => {
for (const device of this.localDevices) {
if (device.mac === deviceUpdate.mac) {
logger.info(`[DevicesService] Updating device "${device.mac}" due to update from driver with new status "${JSON.stringify(deviceUpdate.status)}"`);
device.deviceStatus = deviceUpdate.status;
return;
}
}
});

setInterval(async () => {
logger.info(`[DevicesService] About to scan netwrok changes as activation each ${DETECT_NETWORK_CHANGES_ACTIVATION}`);
try {
await this.rescanNetwork();
} catch (error) {
logger.info(`[DevicesService] Scan network failed ${inspect(error, false, 5)}`);
}
}, DETECT_NETWORK_CHANGES_ACTIVATION.Milliseconds);
}

/**
Expand Down Expand Up @@ -104,4 +128,4 @@ export class DevicesBl {
}
}

export const DevicesBlSingleton = new DevicesBl(DevicesDalSingleton, LocalNetworkReader, ModulesManagerSingltone);
export const devicesService = new DevicesService(DevicesDalSingleton, LocalNetworkReader, modulesManager);
93 changes: 0 additions & 93 deletions backend/src/business-layer/iftttIntegrationBl.ts

This file was deleted.

113 changes: 58 additions & 55 deletions backend/src/business-layer/minionsBl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { MinionsDal, MinionsDalSingleton } from '../data-layer/minionsDal';
import {
DeviceKind,
ErrorResponse,
IftttOnChanged,
LocalNetworkDevice,
Minion,
MinionCalibrate,
Expand All @@ -14,12 +13,15 @@ import {
ProgressStatus,
User,
} from '../models/sharedInterfaces';
import { ModulesManager, ModulesManagerSingltone } from '../modules/modulesManager';
import { ModulesManager, modulesManager } from '../modules/modulesManager';
import { DeepCopy } from '../utilities/deepCopy';
import { logger } from '../utilities/logger';
import { Delay } from '../utilities/sleep';
import { DevicesBl, DevicesBlSingleton } from './devicesBl';
import { Delay, sleep } from '../utilities/sleep';
import { DevicesService, devicesService } from './devicesBl';
import { SyncEvent } from 'ts-events';
import { Duration } from 'unitsnet-js';

const DELAY_FOR_MINIONS_MISSING_DEVICE_SCAN = Duration.FromMinutes(5);

export class MinionsBl {
/**
Expand All @@ -28,7 +30,7 @@ export class MinionsBl {
public minionFeed = new SyncEvent<MinionFeed>();
// Dependencies
private minionsDal: MinionsDal;
private devicesBl: DevicesBl;
private devicesBl: DevicesService;
private modulesManager: ModulesManager;
private scanningStatus: ProgressStatus = 'finished';

Expand All @@ -46,7 +48,7 @@ export class MinionsBl {
* Init minions bl. using dependency injection pattern to allow units testings.
* @param minionsDal Inject the dal instance.
*/
constructor(minionsDal: MinionsDal, devicesBl: DevicesBl, modulesManager: ModulesManager) {
constructor(minionsDal: MinionsDal, devicesBl: DevicesService, modulesManager: ModulesManager) {
this.minionsDal = minionsDal;
this.devicesBl = devicesBl;
this.modulesManager = modulesManager;
Expand Down Expand Up @@ -458,51 +460,6 @@ export class MinionsBl {
await this.modulesManager.refreshModule(originalMinion.device.brand);
}

/**
* Notify minion status changed by ifttt
* @param minionId Minion id.
* @param iftttOnChanged Minion key amd status to set.
*/
public async notifyMinionChangedByIfttt(minionId: string, iftttOnChanged: IftttOnChanged) {
const minion = this.findMinion(minionId);

if (!minion) {
throw {
responseCode: 1404,
message: 'minion not exist',
} as ErrorResponse;
}

/** Make sure the deviceId match to minion deviceId (there is no other authentication!!!) */
if (iftttOnChanged.deviceId !== minion.device.deviceId) {
throw {
responseCode: 5403,
message: 'invalid device id',
} as ErrorResponse;
}

/** Case it's first time update. */
if (!minion.minionStatus[minion.minionType]) {
const initStatus = {
status: 'on',
};
const initMinionStatus = {};
initMinionStatus[minion.minionType] = initStatus;
minion.minionStatus = initMinionStatus as MinionStatus;
}

/** Update the minion status */
minion.minionStatus[minion.minionType].status = iftttOnChanged.newStatus;

/**
* Send minions feed update.
*/
this.minionFeed.post({
event: 'update',
minion,
});
}

/**
* Init minions.
*/
Expand Down Expand Up @@ -533,7 +490,7 @@ export class MinionsBl {
/**
* Let`s modules retrieve updated minions array.
*/
ModulesManagerSingltone.retrieveMinions.setPullMethod(
modulesManager.retrieveMinions.setPullMethod(
async (): Promise<Minion[]> => {
return await this.getMinions();
},
Expand Down Expand Up @@ -569,6 +526,52 @@ export class MinionsBl {

/** Now mark all tasks finished */
this.scanningStatus = 'finished';

this.scanMissingDevices();
}

/**
* In case of network not yet fully discovered due to timing in machine upload or any other reason
* Scan network till all minions IPs will be discovered
* @returns
*/
private async scanMissingDevices() {
while (true) {
// Sleep for a while
await sleep(DELAY_FOR_MINIONS_MISSING_DEVICE_SCAN);
// Get all minion without a valid IP discovered during initialization.
const missingDevices = this.minions.filter(m => !m?.device?.pysicalDevice?.ip);

// If all discovered, abort.
if (missingDevices.length === 0) {
logger.info(`[MinionsBl.scanMissingDevices] All minion has IP, initialization process done`);
return;
}

logger.info(`[MinionsBl.scanMissingDevices] Minions "${missingDevices.map(m => m.minionId).join(',')}" does not have yet IP, about to scan again...`);

try {
// Scan network again
await this.devicesBl.rescanNetwork();

// Try get status for all missing IP minions
for (const minion of missingDevices) {
if (!minion?.device?.pysicalDevice?.ip) {
logger.info(`[MinionsBl.scanMissingDevices] Minion "${minion.minionId}" still dont has IP`);
continue;
}

try {
logger.info(`[MinionsBl.scanMissingDevices] About to read minions "${minion.minionId}" status`);
await this.readMinionStatus(minion);
} catch (error) {
logger.error(`[MinionsBl.scanMissingDevices] Failed to read minions "${minion.minionId}" status "${error.message}" `);
}
}
} catch (error) {
logger.error(`[MinionsBl.scanMissingDevices] Failed to scan network "${error.message}" `);
}
}
}

/**
Expand All @@ -589,7 +592,7 @@ export class MinionsBl {
}

/**
* Read minoin current status.
* Read minion current status.
* @param minion minion to read status for.
*/
private async readMinionStatus(minion: Minion) {
Expand Down Expand Up @@ -628,7 +631,7 @@ export class MinionsBl {

/**
* Find minion in minions array.
* @param minionId minioin id.
* @param minionId minion id.
*/
private findMinion(minionId: string): Minion {
for (const minion of this.minions) {
Expand Down Expand Up @@ -754,4 +757,4 @@ export class MinionsBl {
}
}

export const MinionsBlSingleton = new MinionsBl(MinionsDalSingleton, DevicesBlSingleton, ModulesManagerSingltone);
export const MinionsBlSingleton = new MinionsBl(MinionsDalSingleton, devicesService, modulesManager);
6 changes: 2 additions & 4 deletions backend/src/business-layer/rfBl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import * as request from 'request-promise';
import { Configuration } from '../config';
import { AcCommands, CommandsSet, RollerCommands, ToggleCommands } from '../models/backendInterfaces';
import { CommandsRepoDevice, ErrorResponse, MinionStatus } from '../models/sharedInterfaces';
import { ModulesManager, ModulesManagerSingltone } from '../modules/modulesManager';
import { ModulesManager, modulesManager } from '../modules/modulesManager';
import { logger } from '../utilities/logger';
import { Delay } from '../utilities/sleep';
import { DevicesBl, DevicesBlSingleton } from './devicesBl';
import { MinionsBl, MinionsBlSingleton } from './minionsBl';

export class RfBl {
Expand Down Expand Up @@ -113,4 +111,4 @@ export class RfBl {
}
}

export const RfBlSingleton = new RfBl(MinionsBlSingleton, ModulesManagerSingltone);
export const RfBlSingleton = new RfBl(MinionsBlSingleton, modulesManager);
Loading

0 comments on commit d2b55cf

Please sign in to comment.