Skip to content

Commit

Permalink
Native apps as class - see #130
Browse files Browse the repository at this point in the history
  • Loading branch information
klein0r committed May 10, 2024
1 parent 583283e commit 0a8d51a
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 159 deletions.
3 changes: 2 additions & 1 deletion admin/i18n/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@
"Max": "Max",
"Count": "Count",
"Step size": "Step size",
"seconds": "seconds"
"seconds": "seconds",
"Activate same apps as in main instance": "Activate same apps as in main instance"
}
12 changes: 10 additions & 2 deletions admin/jsonConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,20 @@
"adapter": "awtrix-light",
"allowDeactivate": true,
"sm": 12,
"md": 4,
"lg": 4,
"md": 6,
"lg": 6,
"validator": "data.foreignSettingsInstance === '' || data.foreignSettingsInstance !== `awtrix-light.${_instance}`",
"validatorErrorText": "Select another instance",
"validatorNoSaveOnError": true
},
"foreignSettingsInstanceActivateApps": {
"hidden": "data.foreignSettingsInstance === '' || data.foreignSettingsInstance === `awtrix-light.${_instance}`",
"type": "checkbox",
"label": "Activate same apps as in main instance",
"sm": 12,
"md": 6,
"lg": 6
},
"_headerCustomApps": {
"hidden": "!!data.foreignSettingsInstance",
"newLine": true,
Expand Down
1 change: 1 addition & 0 deletions io-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
"downloadScreenContent": false,
"downloadScreenContentInterval": 10,
"foreignSettingsInstance": "",
"foreignSettingsInstanceActivateApps": false,
"customApps": [],
"ignoreNewValueForAppInTimeRange": 3,
"historyApps": [],
Expand Down
4 changes: 3 additions & 1 deletion src/lib/adapter-config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ declare global {
downloadScreenContent: boolean;
downloadScreenContentInterval: number;
foreignSettingsInstance: string;
foreignSettingsInstanceActivateApps: boolean;
customApps: Array<CustomApp>;
ignoreNewValueForAppInTimeRange: number;
historyApps: Array<HistoryApp>;
Expand All @@ -78,4 +79,5 @@ declare global {
}

// this is required so the above AdapterConfig is found by TypeScript / type checking
export {};
export { };

Check failure on line 82 in src/lib/adapter-config.d.ts

View workflow job for this annotation

GitHub Actions / check-and-lint

Delete `·`

Check failure on line 83 in src/lib/adapter-config.d.ts

View workflow job for this annotation

GitHub Actions / check-and-lint

Delete `⏎`
131 changes: 44 additions & 87 deletions src/lib/app-type/abstract.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import { AwtrixLight } from '../../main';
import { DefaultApp } from '../adapter-config';
import { AwtrixApi } from '../api';

export namespace AppType {
export abstract class AbstractApp {
private definition: DefaultApp;
protected ignoreNewValueForAppInTimeRange: number;
private name: string;

protected apiClient: AwtrixApi.Client;
protected adapter: AwtrixLight;
protected isVisible: boolean;

protected objPrefix: string;

public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, definition: DefaultApp) {
this.definition = definition;
this.ignoreNewValueForAppInTimeRange = adapter.config.ignoreNewValueForAppInTimeRange;
public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, name: string) {
this.name = name;

this.apiClient = apiClient;
this.adapter = adapter;
this.isVisible = false;

if (!this.adapter.config.foreignSettingsInstance) {
if (this.adapter.isMainInstance()) {
this.objPrefix = this.adapter.namespace;
} else {
this.objPrefix = this.adapter.config.foreignSettingsInstance;
Expand All @@ -31,89 +26,56 @@ export namespace AppType {
adapter.on('objectChange', this.onObjectChange.bind(this));
}

public abstract getDescription(): string;

public getName(): string {
return this.definition.name;
return this.name;
}

public isMainInstance(): boolean {
return this.objPrefix === this.adapter.namespace;
return this.adapter.isMainInstance();
}

protected getObjIdOwnNamespace(id: string): string {
return this.adapter.removeNamespace(id.replace(this.objPrefix, this.adapter.namespace));
return this.adapter.removeNamespace(this.isMainInstance() ? id : id.replace(this.objPrefix, this.adapter.namespace));
}

public async init(): Promise<boolean> {
const appName = this.getName();
const appVisibleState = await this.adapter.getForeignStateAsync(`${this.objPrefix}.apps.${appName}.visible`);
this.isVisible = appVisibleState ? !!appVisibleState.val : true;

// Ack if changed while instance was stopped
if (appVisibleState && !appVisibleState?.ack) {
await this.adapter.setStateAsync(`apps.${appName}.visible`, { val: this.isVisible, ack: true, c: 'init' });
}

return this.isVisible;
}

public async refresh(): Promise<boolean> {
if (!this.isVisible && this.apiClient.isConnected()) {
// Hide app automatically
const appName = this.getName();
this.apiClient.removeAppAsync(appName).catch((error) => {
this.adapter.log.warn(`[refreshApp] Unable to remove hidden app "${appName}": ${error}`);
});
}

return this.isVisible && this.apiClient.isConnected();
private hasOwnActivateState(): boolean {
return this.isMainInstance() || !this.adapter.config.foreignSettingsInstanceActivateApps;
}

public async createObjects(): Promise<void> {
const appName = this.getName();

this.adapter.log.debug(`[createObjects] Creating objects for app "${appName}" (${this.isMainInstance() ? 'main' : this.objPrefix})`);

await this.adapter.extendObjectAsync(`apps.${appName}.visible`, {
type: 'state',
common: {
name: {
en: 'Visible',
de: 'Sichtbar',
ru: 'Видимый',
pt: 'Visível',
nl: 'Vertaling',
fr: 'Visible',
it: 'Visibile',
es: 'Visible',
pl: 'Widoczny',
uk: 'Вибрані',
'zh-cn': '不可抗辩',
if (this.hasOwnActivateState()) {
await this.adapter.extendObjectAsync(`apps.${appName}.activate`, {
type: 'state',
common: {
name: {
en: 'Activate',
de: 'Aktivieren',
ru: 'Активировать',
pt: 'Ativar',
nl: 'Activeren',
fr: 'Activer',
it: 'Attivare',
es: 'Activar',
pl: 'Aktywuj',
uk: 'Активувати',
'zh-cn': '启用',
},
type: 'boolean',
role: 'button',
read: false,
write: true,
},
type: 'boolean',
role: 'switch.enable',
read: true,
write: this.isMainInstance(),
def: true,
},
native: {},
});

if (!this.isMainInstance()) {
await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.visible`);
}
}

public async unloadAsync(): Promise<void> {
if (this.adapter.config.removeAppsOnStop) {
this.adapter.log.info(`[onUnload] Deleting app on awtrix light with name "${this.definition.name}"`);

try {
await this.apiClient.removeAppAsync(this.definition.name).catch((error) => {
this.adapter.log.warn(`Unable to remove unknown app "${this.definition.name}": ${error}`);
});
} catch (error) {
this.adapter.log.error(`[onUnload] Unable to delete app ${this.definition.name}: ${error}`);
}
native: {},
});
} else {
await this.adapter.delObjectAsync(`apps.${appName}.activate`);
await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.activate`);
}
}

Expand All @@ -125,20 +87,15 @@ export namespace AppType {
// Handle default states for all apps
if (id && state && !state.ack) {
const appName = this.getName();
const idOwnNamespace = this.getObjIdOwnNamespace(id);

if (id === `${this.objPrefix}.apps.${appName}.visible`) {
if (state.val !== this.isVisible) {
this.adapter.log.debug(`[onStateChange] Visibility of app ${appName} changed to ${state.val}`);

this.isVisible = !!state.val;

await this.refresh();
await this.adapter.setStateAsync(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix}` });
// activate app
if (id === `${this.hasOwnActivateState() ? this.adapter.namespace : this.objPrefix}.apps.${appName}.activate`) {
if (state.val) {
this.apiClient!.requestAsync('switch', 'POST', { name: appName }).catch((error) => {
this.adapter.log.warn(`[onStateChange] (switch) Unable to execute action: ${error}`);
});
} else {
this.adapter.log.debug(`[onStateChange] Visibility of app "${appName}" IGNORED (not changed): ${state.val}`);

await this.adapter.setStateAsync(idOwnNamespace, { val: state.val, ack: true, c: `onStateChange ${this.objPrefix} (unchanged)` });
this.adapter.log.warn(`[onStateChange] Received invalid value for state ${id}`);
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/lib/app-type/custom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AwtrixLight } from '../../main';
import { CustomApp } from '../adapter-config';
import { AwtrixApi } from '../api';
import { AppType as AbstractAppType } from './abstract';
import { AppType as UserAppType } from './user';

export namespace AppType {
type ObjCache = {
Expand All @@ -11,7 +11,7 @@ export namespace AppType {
ts: number;
};

export class Custom extends AbstractAppType.AbstractApp {
export class Custom extends UserAppType.UserApp {
private appDefinition: CustomApp;
private objCache: ObjCache | undefined;
private isStaticText: boolean;
Expand All @@ -28,6 +28,10 @@ export namespace AppType {
this.cooldownTimeout = undefined;
}

public override getDescription(): string {
return 'custom';
}

public override async init(): Promise<boolean> {
const text = String(this.appDefinition.text).trim();
if (text.length > 0) {
Expand Down Expand Up @@ -324,6 +328,8 @@ export namespace AppType {
}

protected override async stateChanged(id: string, state: ioBroker.State | null | undefined): Promise<void> {
super.stateChanged(id, state);

if (this.objCache && !this.isStaticText) {
if (id && state && id === this.appDefinition.objId) {
if (state.ack) {
Expand Down
14 changes: 10 additions & 4 deletions src/lib/app-type/expert.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { AwtrixLight } from '../../main';
import { ExpertApp } from '../adapter-config';
import { AwtrixApi } from '../api';
import { AppType as AbstractAppType } from './abstract';
import { AppType as UserAppType } from './user';

export namespace AppType {
export class Expert extends AbstractAppType.AbstractApp {
export class Expert extends UserAppType.UserApp {
private appDefinition: ExpertApp;
private appStates: { [key: string]: ioBroker.StateValue };
private refreshTimeout: ioBroker.Timeout | undefined;
Expand All @@ -17,6 +17,10 @@ export namespace AppType {
this.refreshTimeout = undefined;
}

public override getDescription(): string {
return 'expert';
}

public override async init(): Promise<boolean> {
const appName = this.getName();

Expand Down Expand Up @@ -71,6 +75,8 @@ export namespace AppType {
}

public async createObjects(): Promise<void> {
await super.createObjects();

const appName = this.getName();

await this.adapter.extendObjectAsync(`apps.${appName}.text`, {
Expand Down Expand Up @@ -215,11 +221,11 @@ export namespace AppType {
await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.icon`);
await this.adapter.subscribeForeignStatesAsync(`${this.objPrefix}.apps.${appName}.duration`);
}

return super.createObjects();
}

protected override async stateChanged(id: string, state: ioBroker.State | null | undefined): Promise<void> {
super.stateChanged(id, state);

// Handle default states for all apps
if (id && state && !state.ack) {
const appName = this.getName();
Expand Down
8 changes: 6 additions & 2 deletions src/lib/app-type/history.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AwtrixLight } from '../../main';
import { HistoryApp } from '../adapter-config';
import { AwtrixApi } from '../api';
import { AppType as AbstractAppType } from './abstract';
import { AppType as UserAppType } from './user';

export namespace AppType {
export type HistoryOptions = {
Expand All @@ -16,7 +16,7 @@ export namespace AppType {
ack: boolean;
};

export class History extends AbstractAppType.AbstractApp {
export class History extends UserAppType.UserApp {
private appDefinition: HistoryApp;
private isValidSourceInstance: boolean;
private isValidObjId: boolean;
Expand All @@ -31,6 +31,10 @@ export namespace AppType {
this.refreshTimeout = undefined;
}

public override getDescription(): string {
return 'history';
}

public override async init(): Promise<boolean> {
if (this.appDefinition.sourceInstance) {
const sourceInstanceObj = await this.adapter.getForeignObjectAsync(`system.adapter.${this.appDefinition.sourceInstance}`);
Expand Down
15 changes: 15 additions & 0 deletions src/lib/app-type/native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AwtrixLight } from '../../main';
import { AwtrixApi } from '../api';
import { AppType as AbstractAppType } from './abstract';

export namespace AppType {
export class Native extends AbstractAppType.AbstractApp {
public constructor(apiClient: AwtrixApi.Client, adapter: AwtrixLight, name: string) {
super(apiClient, adapter, name);
}

public override getDescription(): string {
return 'native';
}
}
}
Loading

0 comments on commit 0a8d51a

Please sign in to comment.