18 Commits
v1.0.0 ... main

Author SHA1 Message Date
a1f42acc45 Merge branch 'main' of https://git.letsstein.de/tom/mqtt_creator 2025-07-30 13:02:56 +02:00
1484209704 added Jenkinsfile to gitignore 2025-07-30 13:02:36 +02:00
tom
ae1fb19f37 Merge pull request 'Merge into Main' (#1) from dev/devices into main
All checks were successful
mqtt_creator/pipeline/head This commit looks good
Reviewed-on: tom/mqtt_creator#1
2024-08-02 08:16:31 +02:00
tom
184bf373ad ADD: heading and close button
All checks were successful
mqtt_creator/pipeline/head This commit looks good
mqtt_creator/pipeline/pr-main This commit looks good
2024-08-02 08:11:39 +02:00
tom
c2c82d73c1 CHANGE: removed some defaults in basemodel, to create less in toJSON 2024-08-02 08:11:08 +02:00
tom
ddadb1f14b ADD: general changes + changelog 2024-08-02 07:35:42 +02:00
tom
0403eb90d3 ADD: Device and Output control 2024-08-02 07:34:57 +02:00
tom
f4eea9bebe CHANGE: Created seperate output for discovery 2024-08-02 07:34:09 +02:00
tom
942142f517 CHANGE: added publish topic functions 2024-08-02 07:10:09 +02:00
tom
e6a49d55e2 CHANGE: seperated mqtt device 2024-08-02 07:09:46 +02:00
tom
f6fb4f6b61 ADD: Discovery Output with code integration 2024-08-02 07:08:50 +02:00
tom
15365cfbfe CHNAGE: added size comparism 2024-08-02 07:07:44 +02:00
tom
f98a3035e1 CHANGE: focus on name input 2024-07-31 13:06:52 +02:00
tom
897120028c CHANGE: autochange topics with custom ending in mind 2024-07-31 13:06:38 +02:00
tom
06e2d77f08 CHANGE: multiple entites 2024-07-30 15:30:35 +02:00
tom
fe60416e8c CHANGE: entity_type in select 2024-07-29 15:35:44 +02:00
tom
bbc4dd6e53 CHANGE: automatic topic generation in class 2024-07-29 15:33:58 +02:00
tom
56365214b8 ADD: components to display entity data 2024-07-29 15:33:39 +02:00
33 changed files with 1120 additions and 471 deletions

View File

@@ -5,4 +5,5 @@
/e2e
/docs
.gitignore
*.zip
*.zip
Jenkinsfile

10
Changelog.md Normal file
View File

@@ -0,0 +1,10 @@
# Changelog
## [1.1.0] - Devices
- code integration for faster development
- device and multi entity support
- dual window view (config + discovery)
- fixed config bug
## [1.0.0] - Initial
- Single Entity creation and discovery creation

View File

@@ -1,7 +1,25 @@
import { DeviceClass, MQTTEntity } from './mqtt_base';
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
export class MqttBinary extends MQTTEntity {
dev_cla: DeviceClass = new DeviceClass([
override ent_type: string = 'binary';
override attrs: Set<string> = new Set([
'name',
'uniq_id',
'stat_t',
'pl_on',
'pl_off',
'dev_cla',
]);
get dev_cla(): DeviceClass {
return this._dev_cla;
}
set dev_cla(data: number) {
this._dev_cla.value = data;
}
_dev_cla: DeviceClass = new DeviceClass([
'battery',
'battery_charging',
'carbon_monoxide',

View File

@@ -0,0 +1,7 @@
import { MqttDevice } from './mqtt-device';
describe('MqttDevice', () => {
it('should create an instance', () => {
expect(new MqttDevice()).toBeTruthy();
});
});

View File

@@ -0,0 +1,52 @@
import { EventEmitter } from "@angular/core";
import { hash } from "./mqtt_base";
export class MQTTDevice {
private _name: string = '';
private _identifier: string = '';
private _serial_number: string = '';
// private configuration_url: string = '';
topic_updates = new EventEmitter<string>();
get name() {
return this._name;
}
set name(name: string) {
this._name = name;
if (name == '') this._serial_number = '';
else this._serial_number = hash(name);
this.topic_updates.next('stat_t');
this.topic_updates.next('cmd_t');
this.topic_updates.next('bri_cmd_t');
}
get serial_number() {
return this._serial_number;
}
set serial_number(serial_number: string) {
this._serial_number = serial_number;
}
get indetifier() {
this._identifier =
this._name == '' && this._serial_number == ''
? ''
: [this._name, this._serial_number].join('_');
return this._identifier;
}
toJSON(full: boolean = false): { [key: string]: string } {
if (full)
return {
...this.toJSON(),
name: this._name,
serial_number: this._serial_number,
};
return {
ids: this._identifier,
};
}
}

View File

@@ -1,9 +1,56 @@
import { MQTTEntity } from './mqtt_base';
export class MqttLight extends MQTTEntity {
cmd_t: string = 'command/topic';
bri_cmd_t: string = 'brightness/command/topic';
pl_on: string = '1';
pl_off: string = '0';
pl_on: string = '';
pl_off: string = '';
val_tpl: string = '';
bri_cmd_t: string = 'brightness/command/topic';
_cmd_t: string = 'command/topic';
override readonly ent_type: string = 'light';
override attrs: Set<string> = new Set([
'name',
'uniq_id',
'stat_t',
'pl_on',
'pl_off',
'val_tpl',
'bri_cmd_t',
'cmd_t',
]);
get cmd_t() {
return this._cmd_t;
}
set cmd_t(data: string) {
this._cmd_t = data;
}
override set name(data: string) {
super.name = data;
this.topic_updates.next('cmd_t');
this.topic_updates.next('bri_cmd_t');
}
override get name() {
return this._name;
}
override set uniq_id(data: string) {
super.uniq_id = data;
this.topic_updates.next('cmd_t');
this.topic_updates.next('bri_cmd_t');
}
override get uniq_id() {
return this._uniq_id;
}
override publish_topics(index: number = 1): string[] {
return [
`String stat_topic_${index} = "${this.stat_t}";`,
`String cmd_topic_${index} = "${this.cmd_t}";`,
`String bri_cmd_topic_${index} = "${this.bri_cmd_t}";`,
];
}
}

View File

@@ -1,5 +1,16 @@
import { MQTTEntity } from './mqtt_base';
import { iMQTTEntityBase, MQTTEntity } from './mqtt_base';
export class MqttSensor extends MQTTEntity {
unit_of_meas: string = 'meassure';
export class MqttSensor extends MQTTEntity implements iMqttSensor {
unit_of_meas: string = '';
override ent_type: string = 'sensor';
override attrs: Set<string> = new Set([
'name',
'uniq_id',
'stat_t',
'unit_of_meas',
]);
}
export interface iMqttSensor extends iMQTTEntityBase {
unit_of_meas: string;
}

View File

@@ -1,8 +1,57 @@
import { DeviceClass, MQTTEntity } from './mqtt_base';
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
export class MqttSwitch extends MQTTEntity {
dev_cla: DeviceClass = new DeviceClass(['switch', 'outlet']);
override ent_type: string = 'switch';
override attrs: Set<string> = new Set([
'name',
'uniq_id',
'stat_t',
'dev_cla',
'cmd_t',
'pl_on',
'pl_off',
]);
get dev_cla(): DeviceClass {
return this._dev_cla;
}
set dev_cla(data: number) {
this._dev_cla.value = data;
}
_dev_cla: DeviceClass = new DeviceClass(['switch', 'outlet']);
cmd_t: string = 'command/topic';
pl_on: string = '1';
pl_off: string = '0';
pl_on: string = '';
pl_off: string = '';
override set name(name: string) {
super.name = name;
this.topic_updates.next('cmd_t');
}
override set uniq_id(uniq_id: string) {
super.uniq_id = uniq_id;
this.topic_updates.next('cmd_t');
}
override get name() {
return this._name;
}
override get uniq_id() {
return this._uniq_id;
}
override publish_topics(index: number = 1): string[] {
return [
`String stat_topic_${index} = "${this.stat_t}";`,
`String cmd_topic_${index} = "${this.cmd_t}";`,
];
}
}
export interface iMqttSwitch extends iMQTTEntityBase {
cmd_t: string;
pl_on: string;
pl_off: string;
dev_cla: DeviceClass;
}

View File

@@ -1,23 +1,58 @@
export class MQTTEntity {
name: string = '';
stat_t: string = '';
uniq_id: string = '';
// dev: MQTTDevice | null = null;
// entity_type: ENTITY_TYPE = 0;
import { EventEmitter, Injectable } from '@angular/core';
setProperty(name: unknown, value: any): void {
if (!this.hasOwnProperty(String(name))) return;
this[name as keyof this] = value;
@Injectable()
export class MQTTEntity implements iMQTTEntityBase {
protected _name: string = '';
protected _stat_t: string = 'state/topic';
protected _uniq_id: string = '';
private _size: number = 0;
attrs = new Set(['name', 'stat_t', 'uniq_id']);
topic_updates = new EventEmitter<string>();
readonly ent_type: string = 'base';
get name() {
return this._name;
}
getProperty(name: string): any {
if (!this.hasOwnProperty(name)) return;
return this[name as keyof this];
set name(name: string) {
this._name = name;
if (name == '') return;
this._uniq_id = name + '_' + hash(name);
this.topic_updates.next('stat_t');
}
toJSON(): any {
let jsonObject: { [key: string]: string } = {};
for (let prop_name of Object.getOwnPropertyNames(this)) {
get stat_t() {
return this._stat_t;
}
set stat_t(stat: string) {
this._stat_t = stat;
}
get uniq_id() {
return this._uniq_id;
}
set uniq_id(uniq_id: string) {
this._uniq_id = uniq_id;
this.topic_updates.next('stat_t');
}
get display_name() {
if (this._uniq_id != '') {
return this._uniq_id;
}
return this._name;
}
publish_topics(index: number = 1): string[] {
return [`String stat_topic_${index} = "${this.stat_t}";`];
}
toJSON(): { [key: string]: string | {} } {
let jsonObject: { [key: string]: string | {} } = {};
for (let prop_name of this.attrs.values()) {
let property = this[prop_name as keyof this];
if (property == '') continue;
jsonObject[prop_name] = String(property);
@@ -25,20 +60,20 @@ export class MQTTEntity {
return jsonObject;
}
// toJSON(): any {
// return {
// name: this.name,
// stat_t: this.stat_t,
// uniq_id: this.uniq_id,
// };
// }
}
get size(): number {
this._size = JSON.stringify(this.toJSON()).length;
return this._size;
}
export class MQTTDevice {
name: string = '';
identifiers: string[] = ['MQTT'];
serial_number: string = '';
configuration_url: string = '';
getProperty(name: string): any {
if (!this.attrs.has(name)) return '';
return this[name as keyof this];
}
setProperty(name: string, value: any) {
if (!this.attrs.has(name)) return;
this[name as keyof this] = value;
}
}
export class DeviceClass {
@@ -53,3 +88,60 @@ export class DeviceClass {
return this.choices[this.value];
}
}
export interface iMQTTEntityBase {
name: string;
stat_t: string;
uniq_id: string;
display_name: string;
attrs: Set<string>;
readonly ent_type: string;
topic_updates: EventEmitter<string>;
publish_topics: (index: number) => string[];
toJSON: () => { [key: string]: string | {} };
}
export function hash(str: string, digits: number = 5): string {
let seed = cyrb128(str);
let rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
let rand_num = Math.floor(rand() * 10 ** digits).toString();
return rand_num;
}
function cyrb128(str: string) {
let h1 = 1779033703,
h2 = 3144134277,
h3 = 1013904242,
h4 = 2773480762;
for (let i = 0, k; i < str.length; i++) {
k = str.charCodeAt(i);
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
}
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
(h1 ^= h2 ^ h3 ^ h4), (h2 ^= h1), (h3 ^= h1), (h4 ^= h1);
return [h1 >>> 0, h2 >>> 0, h3 >>> 0, h4 >>> 0];
}
function sfc32(a: number, b: number, c: number, d: number) {
return function () {
a |= 0;
b |= 0;
c |= 0;
d |= 0;
let t = (((a + b) | 0) + d) | 0;
d = (d + 1) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
}

View File

@@ -1,51 +1,107 @@
import { EventEmitter, Injectable, Input } from '@angular/core';
import { MQTTEntity } from '../_models/mqtt_base';
import { MqttLight } from '../_models/mqtt-light';
import { MqttSwitch } from '../_models/mqtt-switch';
import { MqttSensor } from '../_models/mqtt-sensor';
import { MqttBinary } from '../_models/mqtt-binary';
import { MqttLight } from '../_models/mqtt-light';
import { MqttSensor } from '../_models/mqtt-sensor';
import { MqttSwitch } from '../_models/mqtt-switch';
import { MQTTEntity } from '../_models/mqtt_base';
import { MQTTDevice } from '../_models/mqtt-device';
@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class GeneratorService {
public selected_entity: MQTTEntity | null = null;
public created_enteties: Array<MQTTEntity> = [];
public _selected_entity: MQTTEntity | null = null;
public created_enteties: Set<MQTTEntity> = new Set();
@Input() device_name: string = "";
@Input() device_id: string = "";
@Input() device_standalone: boolean = false;
@Input() upperTopic: string = "";
@Input() auto_topic: number = 2;
updateObserver: EventEmitter<boolean> = new EventEmitter<boolean>();
@Input() auto_topic: boolean = true;
@Input() use_device: boolean = false;
device: MQTTDevice = new MQTTDevice();
_upper_topic: string = '';
constructor() { }
get_properties() {
return Object.getOwnPropertyNames(this.selected_entity);
constructor() {
this.device.topic_updates.subscribe((topic) => {
for (let entity of [...this.created_enteties, this.selected_entity]) {
entity?.setProperty(topic, this.updateTopic(entity, topic));
}
});
}
update(){
this.updateObserver.emit();
get upper_topic(): string {
return this._upper_topic;
}
set upper_topic(value: string) {
this._upper_topic = value;
for (let entity of [...this.created_enteties, this.selected_entity]) {
entity?.setProperty('stat_t', this.updateTopic(entity, 'stat_t'));
entity?.setProperty('cmd_t', this.updateTopic(entity, 'cmd_t'));
entity?.setProperty('bri_cmd_t', this.updateTopic(entity, 'bri_cmd_t'));
}
}
updateObserver: EventEmitter<boolean> = new EventEmitter<boolean>();
set selected_entity(entity: MQTTEntity | null) {
this._selected_entity = entity;
entity?.topic_updates.subscribe((topic: string) => {
if (this.auto_topic)
entity.setProperty(topic, this.updateTopic(entity, topic));
});
}
get selected_entity(): MQTTEntity | null {
return this._selected_entity;
}
get created_entity_num(): number {
return Array.from(this.created_enteties).length;
}
updateTopic(entity: MQTTEntity, topic: string) {
let topicStr: string = entity.getProperty(topic).split('/').pop();
let customTopic =
topicStr == 'topic' || topicStr == topic ? topic : topicStr;
return join(
'/',
this.upper_topic,
this.use_device ? this.device.indetifier : entity.ent_type,
entity.display_name,
customTopic
).toLowerCase();
}
createEntity() {
console.log('Creating entity...');
if (this._selected_entity) {
this.created_enteties.add(this._selected_entity);
this._selected_entity = null;
}
}
deleteEntity(entity: MQTTEntity) {
this.created_enteties.delete(entity);
}
has_property(property: string): boolean {
if (this.selected_entity == null) return false;
if (this.selected_entity.hasOwnProperty(property)) return true;
return false
return false;
}
entity_types: { [id: string]: [string, typeof MQTTEntity] } = {
'0': ['select type', MQTTEntity],
'1': ['light', MqttLight],
'2': ['switch', MqttSwitch],
'3': ['sensor', MqttSensor],
'4': ['binary_sensor', MqttBinary],
};
}
export function randomString(length: number): string {
return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
}
export const entity_types: { [id: string]: [string, typeof MQTTEntity] } = {
'0': ["select type", MQTTEntity],
'1': ["light", MqttLight],
'2': ["switch", MqttSwitch],
'3': ["sensor", MqttSensor],
'4': ["binary_sensor", MqttBinary]
function join(seperator = '/', first: string, ...args: string[]): string {
let result = '';
for (let str of args) {
if (str == '') continue;
result += seperator + str;
}
if (first == '') return result.slice(1);
return first + result;
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { OutputService } from './output.service';
describe('OutputService', () => {
let service: OutputService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(OutputService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,119 @@
import { Injectable } from '@angular/core';
import { MQTTEntity } from '../_models/mqtt_base';
import { GeneratorService } from './generator.service';
@Injectable({
providedIn: 'root',
})
export class OutputService {
constructor(private generatorService: GeneratorService) {}
basecode: {} = { 'mqtt-init': '{testestsdsdf}' };
output: boolean = false;
_integrated_output: boolean = false;
_device_entity_cached: { entity: MQTTEntity; listlen: number } = {
entity: new MQTTEntity(),
listlen: 0,
};
get device_entity(): MQTTEntity {
if (
this._device_entity_cached.listlen ===
this.generatorService.created_entity_num
)
return this._device_entity_cached.entity;
let entites = Array.from(this.generatorService.created_enteties).sort(
(a, b) => {
return a.size - b.size;
}
);
this._device_entity_cached = {
entity: entites[0],
listlen: this.generatorService.created_entity_num,
};
console.log('sizing');
console.log(this._device_entity_cached);
return entites[0];
}
get seperate_outputs(): boolean {
return this.output && !this._integrated_output;
}
get integrated_output(): boolean {
return this.output && this._integrated_output;
}
set integrated_output(value: boolean) {
this._integrated_output = value;
}
getDiscoveryString(entity: MQTTEntity, escaped = false): string | {} {
let str = entity.toJSON();
if (this.generatorService.use_device) {
str['dev'] = this.generatorService.device.toJSON(
entity.uniq_id == this.device_entity.uniq_id
);
}
if (escaped) {
return JSON.stringify(str).replaceAll('"', '\\"');
}
return str;
}
getDiscoveryTopic(entity: MQTTEntity): string {
return join(
'/',
'homeassistant',
entity.ent_type,
entity.display_name,
'config'
).toLowerCase();
}
integrateCode(): string {
let mqtt_init =
'#include <EspMQTTClient.h>\n\nEspMQTTClient mqtt(\n\t"wifi_name",\n\t"wifi_pw",\n\t"broker_ip",\n\t"mqtt_acc",\n\t"mqtt_pw",\n\t"device_name",\n\t"mqtt_port"\n);\n';
let setup = 'void setup(){\nclient.setMaxPacketSize(512);\n}\n';
let onConnectStart = 'void onConnectionEstablished() {\n\tdelay=10\n';
let onConnectEnd = '\tmqtt.loop()\n}\n';
let loop = 'void loop() {\n\tmqtt.loop();\n\tdelay(100);\n}';
let discoveryMsg: string[] = [];
let publishTopics: string[] = [];
for (let [index, entity] of Array.from(
this.generatorService.created_enteties
).entries()) {
discoveryMsg.push('\t// Sending discovery for Entity ' + index);
discoveryMsg.push(
`\tmqtt.publish(${this.getDiscoveryTopic(
entity
)}, ${this.getDiscoveryString(entity, true)}, true);\n`
);
publishTopics.push(...entity.publish_topics(index));
publishTopics.push();
}
publishTopics.push('\n');
return (
mqtt_init +
'\n' +
publishTopics.join('\n') +
setup +
onConnectStart +
discoveryMsg.join('\n') +
onConnectEnd +
loop
);
}
}
function join(seperator = '/', first: string, ...args: string[]): string {
let result = '';
for (let str of args) {
if (str == '') continue;
result += seperator + str;
}
if (first == '') return result.slice(1);
return first + result;
}

View File

@@ -3,7 +3,7 @@
<div class="flex flex-row justify-left gap-4 items-center h-12 mb-2 px-2">
<img src="mqtt_creator.ico" alt="app icon" class="h-full">
<h2 class="text-3xl font-bold text-myButton text-nowrap" >MQTT Discovery Creator</h2>
<p class="w-auto text-nowrap w-full">v.1.0</p>
<p class="w-auto text-nowrap w-full">v.1.1</p>
<a class="w-auto text-nowrap hover:text-myButton" href="http://www.tomtroeger.de">My Homepage</a>
<a class="w-auto text-nowrap hover:text-myButton" href="https://git.letsstein.de/tom/mqtt_creator">Code</a>
</div>

View File

@@ -1,26 +1,27 @@
import { NgModule } from '@angular/core';
import { BrowserModule, provideClientHydration } from '@angular/platform-browser';
import {
BrowserModule,
provideClientHydration,
} from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { GeneratorComponent } from './generator/generator.component';
import { FormsModule } from '@angular/forms';
import { EntityComponent } from './entity/entity.component';
import { EntityDataComponent } from './entity-data/entity-data.component';
import { EntityOutputComponent } from './entity-output/entity-output.component';
import { OutputComponent } from './output/output.component';
@NgModule({
declarations: [
AppComponent,
GeneratorComponent,
EntityComponent
EntityDataComponent,
EntityOutputComponent,
OutputComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
],
providers: [
provideClientHydration()
],
bootstrap: [AppComponent]
imports: [BrowserModule, AppRoutingModule, FormsModule],
providers: [provideClientHydration()],
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}

View File

@@ -0,0 +1,18 @@
.heading {
display: flex;
position: inherit;
margin-bottom: 0.25rem;
}
.heading button {
padding: 0.25rem;
min-width: 0;
position: absolute;
right: 0;
top: 0;
}
.heading h2 {
width: 100%;
text-align: center;
}

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html5>
<div class="heading" *ngIf="created" >
<h2>entity {{ent_index}} - {{basemodel.ent_type}}</h2>
<button (click)="generatorService.deleteEntity(basemodel)" ><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/>
</svg>
</button>
</div>
<form class="grid grid-cols-2 gap-4" (submit)="$event.preventDefault();generatorService.createEntity()" >
<div class="property" *ngIf="hasProperty('name')">
<p>Name</p>
<input tabindex="0" #nameinput autocomplete="name" placeholder="entity name" type="text" name="entity_name" [ngModel]="entity_name" (ngModelChange)="entity_name = $event"/>
</div>
<div class="property" *ngIf="hasProperty('uniq_id')">
<p>Uniqe ID</p>
<div class="flex-row flex gap-2">
<input type="text" name="entity_uniq_id" [ngModel]="entity_uniq_id" (ngModelChange)="entity_uniq_id = $event"/>
</div>
</div>
<div class="property" *ngIf="hasProperty('cmd_t')">
<p>Command Topic</p>
<input placeholder="command/topic" type="text" name="entity_cmd_t" [ngModel]="entity_cmd_t" (ngModelChange)="entity_cmd_t = $event" /> <!-- (input)="lock_auto_topic(); update('entity_cmd_t', $event)"-->
</div>
<div class="property" *ngIf="hasProperty('bri_cmd_t')">
<p>Brightness Command Topic</p>
<input placeholder="brightness/command/topic" type="text" name="entity_bri_cmd_t" [ngModel]="entity_bri_cmd_t" (ngModelChange)="entity_bri_cmd_t = $event" /> <!--(ngModelChange)="lock_auto_topic(); update('entity_bri_cmd_t', $event)"-->
</div>
<div class="property !flex-row" *ngIf="hasProperty('pl_off') || hasProperty('pl_on')">
<div *ngIf="hasProperty('pl_on')">
<p>Payload on</p>
<input placeholder="1" type="text" name="entity_pl_on" [ngModel]="entity_pl_on" (ngModelChange)="entity_pl_on = $event"/>
</div>
<div *ngIf="hasProperty('pl_off')">
<p>Payload off</p>
<input placeholder="0" type="text" name="entity_pl_off" [ngModel]="entity_pl_off" (ngModelChange)="entity_pl_off = $event"/>
</div>
</div>
<div class="property" *ngIf="hasProperty('unit_of_meas')">
<p>Unit of meassurement</p>
<input placeholder="°C" type="text" name="entity_unit_of_meas" [ngModel]="entity_unit_of_meas" (ngModelChange)="entity_unit_of_meas = $event"/>
</div>
<div class="property" *ngIf="hasProperty('val_tpl')">
<p>Value Template</p>
<input type="text" name="entity_val_tpl" [ngModel]="entity_val_tpl" (ngModelChange)="entity_val_tpl = $event"/>
</div>
<div class="property " *ngIf="hasProperty('dev_cla')">
<p>Device Class</p>
<select name="entity_dev_cla" [ngModel]="entity_dev_cla" (ngModelChange)="entity_dev_cla =$event">
<option *ngFor="let choice of basemodel.getProperty('dev_cla').choices" value="{{basemodel.getProperty('dev_cla').choices.indexOf(choice)}}">{{choice}}</option>
</select>
</div>
<div class="property" *ngIf="hasProperty('stat_t')">
<p>State Topic</p>
<div class="flex-row flex gap-2">
<input placeholder="state/topic" type="text" name="stat_t" [ngModel]="basemodel.getProperty('stat_t')" (ngModelChange)="basemodel.stat_t= $event" /> <!--(ngModelChange)="lock_auto_topic(); update('stat_t', $event)"-->
</div>
</div>
<div class="property col-span-2" *ngIf="!created">
<button class="w-full" (click)="generatorService.createEntity()">Add Entity</button>
</div>
</form>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EntityDataComponent } from './entity-data.component';
describe('EntityDataComponent', () => {
let component: EntityDataComponent;
let fixture: ComponentFixture<EntityDataComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EntityDataComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EntityDataComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,157 @@
import { Component, Input, ViewChild } from '@angular/core';
import { DeviceClass, MQTTEntity } from '../_models/mqtt_base';
import { GeneratorService } from '../_services/generator.service';
@Component({
selector: 'app-entity-data',
templateUrl: './entity-data.component.html',
styleUrl: './entity-data.component.css',
})
export class EntityDataComponent {
@Input() entity_type: number = 1;
@Input() basemodel!: MQTTEntity;
@Input() created: boolean = false;
@Input() ent_index: number = 0;
@ViewChild('nameinput') nameinput!: any;
constructor(public generatorService: GeneratorService) {
if (generatorService.selected_entity) {
this.basemodel = generatorService.selected_entity;
}
}
ngAfterViewInit() {
if (!this.created) this.nameinput.nativeElement.focus();
}
_entity_name: string = '';
_entity_uniq_id: string = '';
_entity_stat_t: string = '';
_entity_cmd_t: string = '';
_entity_bri_cmd_t: string = '';
_entity_pl_off: string = '';
_entity_pl_on: string = '';
_entity_unit_of_meas: string = '';
_entity_val_tpl: string = '';
_entity_dev_cla: DeviceClass = new DeviceClass([]);
get entity_name() {
let base = this.basemodel.getProperty('name');
if (base == '') {
this.basemodel.setProperty('name', this._entity_name);
return this._entity_name;
}
return base;
}
set entity_name(data: string) {
this._entity_name = data;
this.basemodel.setProperty('name', data);
}
get entity_uniq_id() {
let base = this.basemodel.getProperty('uniq_id');
if (base == '') {
this.basemodel.setProperty('uniq_id', this._entity_uniq_id);
return this._entity_uniq_id;
}
return base;
}
set entity_uniq_id(data: string) {
this._entity_uniq_id = data;
this.basemodel.setProperty('uniq_id', data);
}
get entity_stat_t() {
let base = this.basemodel.getProperty('stat_t');
if (base == 'state/topic') {
this.basemodel.setProperty('stat_t', this._entity_stat_t);
return this._entity_stat_t;
}
return base;
}
set entity_stat_t(data: string) {
this._entity_stat_t = data;
this.basemodel.setProperty('stat_t', data);
}
get entity_cmd_t() {
let base = this.basemodel.getProperty('cmd_t');
if (base == 'command/topic') {
this.basemodel.setProperty('cmd_t', this._entity_cmd_t);
return this._entity_cmd_t;
}
return base;
}
set entity_cmd_t(data: string) {
this._entity_cmd_t = data;
this.basemodel.setProperty('cmd_t', data);
}
get entity_bri_cmd_t() {
let base = this.basemodel.getProperty('bri_cmd_t');
if (base == 'brightness/command/topic') {
this.basemodel.setProperty('bri_cmd_t', this._entity_bri_cmd_t);
return this._entity_bri_cmd_t;
}
return base;
}
set entity_bri_cmd_t(data: string) {
this._entity_bri_cmd_t = data;
this.basemodel.setProperty('bri_cmd_t', data);
}
get entity_pl_off() {
return this._entity_pl_off;
}
set entity_pl_off(data: string) {
this._entity_pl_off = data;
this.basemodel.setProperty('pl_off', data);
}
get entity_pl_on() {
return this._entity_pl_on;
}
set entity_pl_on(data: string) {
this._entity_pl_on = data;
this.basemodel.setProperty('pl_on', data);
}
get entity_unit_of_meas() {
return this._entity_unit_of_meas;
}
set entity_unit_of_meas(data: string) {
this._entity_unit_of_meas = data;
this.basemodel.setProperty('unit_of_meas', data);
}
get entity_val_tpl() {
return this._entity_val_tpl;
}
set entity_val_tpl(data: string) {
this._entity_val_tpl = data;
this.basemodel.setProperty('val_tpl', data);
}
get entity_dev_cla(): number {
this._entity_dev_cla = this.basemodel.getProperty('dev_cla');
return this._entity_dev_cla.value;
}
set entity_dev_cla(data: number) {
data = Number(data);
this._entity_dev_cla.value = data;
this.basemodel.setProperty('dev_cla', data);
}
hasProperty(property: string) {
return this.basemodel.attrs.has(property);
}
}

View File

@@ -0,0 +1,5 @@
.jsonPrev {
max-width: 50%;
text-wrap: wrap;
flex-shrink: 0;
}

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<div class="property" >
<p>Discovery String</p>
<div class="flex flex-row gap-2">
<pre class="jsonPrev" >{{outputService.getDiscoveryString(basemodel) | json}}</pre>
<span contenteditable>{{outputService.getDiscoveryString(basemodel, true)}}</span>
</div>
</div>
<div class="property" >
<p>Discovery Topic</p>
<input type="text" readonly [ngModel]="outputService.getDiscoveryTopic(basemodel)">
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EntityOutputComponent } from './entity-output.component';
describe('EntityOutputComponent', () => {
let component: EntityOutputComponent;
let fixture: ComponentFixture<EntityOutputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EntityOutputComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EntityOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
import { MQTTEntity } from '../_models/mqtt_base';
import { OutputService } from '../_services/output.service';
@Component({
selector: 'app-entity-output',
templateUrl: './entity-output.component.html',
styleUrl: './entity-output.component.css',
})
export class EntityOutputComponent {
constructor(public outputService: OutputService) {}
@Input() basemodel!: MQTTEntity;
}

View File

@@ -1,3 +0,0 @@
pre{
}

View File

@@ -1,80 +0,0 @@
<main class="grid grid-cols-2 gap-4">
<!-- Entity -->
<div class="col-span-2 property">
<h3>EntityTyp:</h3>
<select [ngModel]="entity_type" (ngModelChange)="select_type($event)">
<option *ngFor="let key of useObject.keys(entities)" value="{{key}}">{{entities[key][0]}}</option>
</select>
</div>
<div class="property" *ngIf="generatorService.has_property('name')">
<p>Name</p>
<input required placeholder="Name here" type="text"[ngModel]="entity_name" (ngModelChange)="entity_name = $event; update()"/>
</div>
<div class="property" *ngIf="generatorService.has_property('uniq_id')">
<p>Uniqe ID</p>
<div class="flex-row flex gap-2">
<input type="text"[ngModel]="entity_uniq_id" (ngModelChange)="entity_uniq_id = $event; update()"/>
<button (click)="entity_uniq_id = useRandomString(10); update()" class="randomButton" >
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dice-3" viewBox="0 0 16 16">
<path d="M13 1a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2zM3 0a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V3a3 3 0 0 0-3-3z"/>
<path d="M5.5 4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m8 8a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m-4-4a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0"/>
</svg>
</button>
</div>
</div>
<div class="property" *ngIf="generatorService.has_property('cmd_t')">
<p>Command Topic</p>
<input type="text" [ngModel]="entity_cmd_t" (input)="lock_auto_topic(); update('entity_cmd_t', $event)"/>
</div>
<div class="property" *ngIf="generatorService.has_property('bri_cmd_t')">
<p>Brightness Command Topic</p>
<input type="text" [ngModel]="entity_bri_cmd_t" (ngModelChange)="lock_auto_topic(); update('entity_bri_cmd_t', $event)"/>
</div>
<div class="property !flex-row" *ngIf="generatorService.has_property('pl_off') || generatorService.has_property('pl_on')">
<div *ngIf="generatorService.has_property('pl_off')">
<p>Payload off</p>
<input type="text" [ngModel]="entity_pl_off" (ngModelChange)="entity_pl_off = $event"/>
</div>
<div *ngIf="generatorService.has_property('pl_on')">
<p>Payload on</p>
<input type="text" [ngModel]="entity_pl_on" (ngModelChange)="entity_pl_on = $event"/>
</div>
</div>
<div class="property" *ngIf="generatorService.has_property('unit_of_meas')">
<p>Unit of meassurement</p>
<input type="text" [ngModel]="entity_unit_of_meas" (ngModelChange)="entity_unit_of_meas = $event"/>
</div>
<div class="property" *ngIf="generatorService.has_property('val_tpl')">
<p>Value Template</p>
<input type="text" [ngModel]="entity_val_tpl" (ngModelChange)="entity_val_tpl = $event"/>
</div>
<div class="property bg-mySecondary" *ngIf="generatorService.has_property('dev_cla')">
<p>Device Class</p>
<select [ngModel]="entity_dev_cla.value" (ngModelChange)="entity_dev_cla.value =$event">
<option *ngFor="let choice of entity_dev_cla.choices" value="{{entity_dev_cla.choices.indexOf(choice)}}">{{choice}}</option>
</select>
</div>
<div class="property" *ngIf="generatorService.has_property('stat_t')">
<p>State Topic</p>
<div class="flex-row flex gap-2">
<input type="text" [ngModel]="stat_t" (ngModelChange)="lock_auto_topic(); update('stat_t', $event)"/>
</div>
</div>
<button class="col-span-2 py-1" (click)="create_entity()" >Create Entity</button>
<ng-container *ngIf="showDiscovery">
<div class="col-span-2 property" >
<p>Discovery String</p>
<div class="flex flex-row gap-2">
<pre>{{discoveryString | json}}</pre>
<span contenteditable>{{JSONdiscoveryString}}</span>
</div>
</div>
<div class="col-span-2 property" >
<p>Discovery Topic</p>
<input type="text" readonly [ngModel]="_discoveryTopic">
</div>
</ng-container>
</main>

View File

@@ -1,177 +0,0 @@
import { Component, Input } from '@angular/core';
import {
GeneratorService,
entity_types,
randomString,
} from '../_services/generator.service';
import { MqttBinary } from '../_models/mqtt-binary';
import { DeviceClass, MQTTEntity } from '../_models/mqtt_base';
@Component({
selector: 'app-entity',
templateUrl: './entity.component.html',
styleUrl: './entity.component.css',
})
export class EntityComponent {
constructor(public generatorService: GeneratorService) {
generatorService.updateObserver.subscribe((date) => {
this.update();
});
}
readonly useObject = Object;
readonly entities = entity_types;
readonly useRandomString = randomString;
showDiscovery: boolean = false;
_discoveryTopic: string = '';
@Input() entity_type: number = 0;
@Input() stat_t: string = '';
@Input() basemodel: MQTTEntity | null = null;
@Input() entity_name: string = '';
@Input() entity_uniq_id: string = '';
@Input() entity_cmd_t: string = '';
@Input() entity_bri_cmd_t: string = '';
@Input() entity_pl_off: string = '0';
@Input() entity_pl_on: string = '1';
@Input() entity_unit_of_meas: string = '';
@Input() entity_val_tpl: string = '';
@Input() entity_dev_cla: DeviceClass = new DeviceClass([]);
get display_name() {
if (this.entity_name != '' && this.entity_uniq_id != '') {
return this.entity_name + '_' + this.entity_uniq_id;
} else {
return this.entity_name;
}
}
update(model: keyof this = 'stat_t', event: Event | boolean = false): void {
if (this.generatorService.auto_topic == 2) {
this.stat_t = this.updateTopic('stat');
this.entity_cmd_t = this.updateTopic('cmd');
this.entity_bri_cmd_t = this.updateTopic('bri_cmd');
return;
}
if (event instanceof Event)
var value = (event.target as HTMLInputElement).value;
else return;
if (this.generatorService.auto_topic == 1) {
this.sync_topcis(model, value);
} else {
if (!this.hasOwnProperty(model)) return;
if (typeof this[model] === 'string') {
this[model] = value as (typeof this)[keyof this];
}
}
}
sync_topcis(model: keyof this, value: string) {
let new_list = value.split('/');
let old_list = String(this[model]).split('/');
let missing = new_list.filter((item) => old_list.indexOf(item) < 0);
if (old_list.filter((item) => new_list.indexOf(item) < 0).length > 1)
return (this.generatorService.auto_topic = 0);
if (missing.length == 0) return; //(this.generatorService.auto_topic = 2);
if (new_list.indexOf(missing[0]) == new_list.length - 1) {
this[model] = value as (typeof this)[keyof this];
return;
}
let missing_index = new_list.indexOf(missing[0]);
this.stat_t = this.replaceComponent(this.stat_t, missing[0], missing_index);
this.entity_cmd_t = this.replaceComponent(
this.entity_cmd_t,
missing[0],
missing_index
);
this.entity_bri_cmd_t = this.replaceComponent(
this.entity_bri_cmd_t,
missing[0],
missing_index
);
return;
}
replaceComponent(object: string, value: string, index: number) {
let param_list = object.split('/');
param_list[index] = value;
return param_list.join('/');
}
updateTopic(topic: string) {
let topic_str = join(
'/',
this.generatorService.upperTopic,
entity_types[this.entity_type][0],
this.generatorService.device_name,
this.display_name,
topic
).toLowerCase();
return topic_str;
}
select_type(event: unknown) {
let ent_type = entity_types[event as keyof typeof entity_types];
let ent_class = ent_type[1];
if (typeof ent_class === 'function' && event != 0) {
this.basemodel = new ent_class();
this.generatorService.selected_entity = this.basemodel;
if (this.generatorService.has_property('dev_cla')) {
this.entity_dev_cla = this.basemodel.getProperty('dev_cla');
}
} else this.generatorService.selected_entity = null;
this.entity_type = event as number;
this.update();
}
lock_auto_topic() {
this.generatorService.auto_topic = 1;
}
create_entity() {
this.basemodel?.setProperty('stat_t', this.stat_t);
this.basemodel?.setProperty('name', this.entity_name);
this.basemodel?.setProperty('cmd_t', this.entity_cmd_t);
this.basemodel?.setProperty('bri_cmd_t', this.entity_bri_cmd_t);
this.basemodel?.setProperty('pl_off', this.entity_pl_off);
this.basemodel?.setProperty('pl_on', this.entity_pl_on);
this.basemodel?.setProperty('unit_of_meas', this.entity_unit_of_meas);
this.basemodel?.setProperty('val_tpl', this.entity_val_tpl);
this.basemodel?.setProperty('dev_cla', this.entity_dev_cla);
this._discoveryTopic = this.discoveryTopic;
this.showDiscovery = true;
return;
}
get discoveryString() {
return this.basemodel?.toJSON();
}
get JSONdiscoveryString() {
return JSON.stringify(this.basemodel?.toJSON()).replaceAll('"', '\\"');
}
get discoveryTopic() {
if (this.entity_type == 0) return '';
return join(
'/',
'homeassistant',
entity_types[this.entity_type][0],
this.display_name
).toLowerCase();
}
}
function join(seperator = '/', first: string, ...args: string[]): string {
let result = '';
for (let str of args) {
if (str == '') continue;
result += seperator + str;
}
if (first == '') return result.slice(1);
return first + result;
}

View File

@@ -1,32 +1,53 @@
#mainContainer {
display: grid;
grid-template-columns: 3fr 1fr;
gap: 0.5rem;
transition: all 0.25s;
}
.genContainer,
.outContainer {
background: #9d9d9d;
border-radius: 1rem;
padding: 0.75rem;
gap: 1rem;
}
.genContainer {
background: #9D9D9D;
border-radius: 1rem;
padding: 1rem .8rem ;
display: flex;
flex-direction: column;
gap: 1rem;
grid-column: 1;
position: relative;
}
.customCheckboxContainer{
display: flex;
flex-direction: row;
gap: .5rem;
.outContainer {
display: flex;
flex-direction: column;
grid-column: 2;
}
.customCheckboxContainer input[type="radio"]{
display: none;
.customCheckboxContainer {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.customCheckbox{
width: 100%;
padding: .25rem 0;
text-align: center;
background: #B3B3B3;
transition: background .25s ease-in-out, color .25s ease-in-out;
user-select: none;
.customCheckboxContainer input {
display: none;
}
.customCheckboxContainer input:checked + label{
background: var(--accent);
color: var(--secondary);
}
.customCheckbox {
width: 100%;
padding: 0.25rem 0;
text-align: center;
background: #b3b3b3;
transition: background 0.25s ease-in-out, color 0.25s ease-in-out;
user-select: none;
}
.customCheckboxContainer input:checked + label {
background: var(--accent);
color: var(--secondary);
}
.outEnable {
grid-template-columns: 1fr 1fr !important;
}

View File

@@ -1,28 +1,74 @@
<!DOCTYPE html>
<div class="flex flex-col gap-2">
<div class="genContainer">
<div id="mainContainer" class="" [ngClass]="{'outEnable':outputService.output}">
<div class="genContainer grid grid-cols-2 gap-4">
<!-- Statetopic -->
<!-- <h3>Data Channel</h3> -->
<div class="property">
<div class="property col-span-2">
<p>Bereich</p>
<input type="text" [ngModel]="generatorService.upperTopic"
(ngModelChange)="generatorService.upperTopic = $event; generatorService.update()" />
<input type="text" [ngModel]="generatorService.upper_topic"
(ngModelChange)="generatorService.upper_topic = $event" />
</div>
<div class="property">
<p>Automatische Topics</p>
<div class="customCheckboxContainer">
<input [ngModel]="auto_topic" (ngModelChange)="auto_topic=$event" id="auto_topic_0" type="radio" name="auto_topic" value="2">
<label class="customCheckbox" for="auto_topic_0">Alles</label>
<input [ngModel]="auto_topic" (ngModelChange)="auto_topic=$event" id="auto_topic_1" type="radio" name="auto_topic" value="1">
<label class="customCheckbox" for="auto_topic_1">Änderungen übernehmen</label>
<input [ngModel]="auto_topic" (ngModelChange)="auto_topic=$event" id="auto_topic_2" type="radio" name="auto_topic" value="0">
<label class="customCheckbox" for="auto_topic_2">Aus</label>
<div class="property" >
<p>Device</p>
<div class="customCheckboxContainer" >
<input [ngModel]="generatorService.use_device" (ngModelChange)="generatorService.use_device = $event" id="device" type="checkbox">
<label class="customCheckbox" for="device" >combine in device</label>
</div>
</div>
<div class="property">
<p>Automatic Topics</p>
<div class="customCheckboxContainer">
<input [ngModel]="generatorService.auto_topic" (ngModelChange)="generatorService.auto_topic=$event" id="auto_topic" type="checkbox" name="auto_topic">
<label class="customCheckbox" for="auto_topic">automatically generate mqtt topics</label>
</div>
</div>
<ng-container *ngIf="generatorService.use_device">
<div class="property">
<p>Device Name</p>
<input type="text" [ngModel]="device.name" (ngModelChange)="device.name = $event" >
</div>
<div class="property">
<p>Device Identifier</p>
<input type="text" [ngModel]="device.indetifier">
</div>
<!-- <div class="property">
<p>Device Serial Number</p>
</div>
<div class="property">
<p>Device Config Url</p>
</div> -->
</ng-container>
<div class="col-span-2 property">
<h3>EntityTyp:</h3>
<select #typeinput autofocus [ngModel]="entity_type" (ngModelChange)="select_type($event)">
<option *ngFor="let key of useObject.keys(generatorService.entity_types)" value="{{key}}">{{generatorService.entity_types[key][0]}}</option>
</select>
</div>
</div>
<app-entity class="genContainer" ></app-entity>
<div class="outContainer">
<h2>Output</h2>
<div class="customCheckboxContainer">
<input id="output" type="checkbox" [ngModel]="outputService.output" (ngModelChange)="outputService.output = $event">
<label class="customCheckbox" for="output" >show output</label>
</div>
<p>Seperate Output</p>
<div class="customCheckboxContainer">
<input id="seperate_output" type="checkbox" [ngModel]="outputService.integrated_output" (ngModelChange)="outputService.integrated_output = $event">
<label class="customCheckbox" for="seperate_output">Integrate into Code</label>
</div>
</div>
<ng-container *ngIf="generatorService.selected_entity == null" >
<button (click)="select_type(entity_type)" class="genContainer !col-span-2" id="entityPlaceholder">Create new entity</button>
</ng-container>
<app-entity-data class="genContainer" [basemodel]="generatorService.selected_entity" *ngIf="generatorService.selected_entity != null" ></app-entity-data>
<app-output class="outContainer" [ngStyle]="{'grid-row': codeSpan}" *ngIf="outputService.integrated_output" ></app-output>
<ng-container *ngFor="let entity of created_entities.reverse()">
<app-entity-data class="genContainer" [basemodel]="entity" [created]="true" [ent_index]="created_entities.indexOf(entity)" ></app-entity-data>
<app-entity-output class="outContainer" *ngIf="outputService.seperate_outputs" [basemodel]="entity" ></app-entity-output>
</ng-container>
</div>

View File

@@ -1,44 +1,49 @@
import { Component, Input } from '@angular/core';
import { MqttLight } from '../_models/mqtt-light';
import { MqttSwitch } from '../_models/mqtt-switch';
import { MqttSensor } from '../_models/mqtt-sensor';
import { MqttBinary } from '../_models/mqtt-binary';
import { GeneratorService, entity_types, randomString } from '../_services/generator.service';
import { Component, Input, ViewChild } from '@angular/core';
import { GeneratorService } from '../_services/generator.service';
import { MQTTEntity } from '../_models/mqtt_base';
import { OutputService } from '../_services/output.service';
import { MQTTDevice } from '../_models/mqtt-device';
@Component({
selector: 'app-home',
templateUrl: './generator.component.html',
styleUrl: './generator.component.css'
styleUrl: './generator.component.css',
})
export class GeneratorComponent {
constructor(public generatorService: GeneratorService) {
}
constructor(
public generatorService: GeneratorService,
public outputService: OutputService
) {}
readonly useObject = Object;
readonly useRandomString = randomString;
set auto_topic(value: number) {
this.generatorService.auto_topic = value;
console.log(this.generatorService.auto_topic);
@ViewChild('typeinput') typeinput: any;
@Input() entity_type: number = 0;
device: MQTTDevice = this.generatorService.device;
get created_entities(): MQTTEntity[] {
return Array.from(this.generatorService.created_enteties);
}
get auto_topic(): string {
return String(this.generatorService.auto_topic);
get codeSpan(): string {
let start = this.generatorService.selected_entity == null ? 3 : 2;
return `${start}/${3 + this.generatorService.created_entity_num}`;
}
log(event:any){
console.log(Number(event));
select_type(entity_num: number) {
this.entity_type = entity_num;
let entity_type =
this.generatorService.entity_types[
entity_num as keyof typeof this.generatorService.entity_types
];
let entity_class = entity_type[1];
if (typeof entity_class === 'function' && entity_num != 0) {
this.generatorService.selected_entity = new entity_class();
} else {
this.generatorService.selected_entity = null;
this.typeinput.nativeElement.focus();
}
}
}

View File

@@ -0,0 +1,5 @@
pre {
max-width: calc(50vw - 3rem);
overflow-x: scroll;
text-wrap: nowrap;
}

View File

@@ -0,0 +1,5 @@
<div>
<h2>Code Output</h2>
<pre>{{outputService.integrateCode()}}</pre>
<p></p>
</div>

View File

@@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EntityComponent } from './entity.component';
import { OutputComponent } from './output.component';
describe('EntityComponent', () => {
let component: EntityComponent;
let fixture: ComponentFixture<EntityComponent>;
describe('OutputComponent', () => {
let component: OutputComponent;
let fixture: ComponentFixture<OutputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [EntityComponent]
declarations: [OutputComponent]
})
.compileComponents();
fixture = TestBed.createComponent(EntityComponent);
fixture = TestBed.createComponent(OutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { OutputService } from '../_services/output.service';
@Component({
selector: 'app-output',
templateUrl: './output.component.html',
styleUrl: './output.component.css',
})
export class OutputComponent {
constructor(public outputService: OutputService) {}
}

View File

@@ -3,81 +3,105 @@
@tailwind components;
@tailwind utilities;
:root{
--accent: #4cb926;
--text: #535353;
--primary: #b3b3b3;
--secondary: #f8f8f8;
:root {
--accent: #4cb926;
--text: #3d3d3d;
--primary: #b3b3b3;
--secondary: #f8f8f8;
--placeholder: #777;
--input_bg: #b3b3b3;
}
input, select, label, pre {
background: var(--primary);
padding: .25rem .75rem;
border-radius: 1rem;
input,
select,
label,
pre,
button {
background: var(--primary);
padding: 0.25rem 0.75rem;
border-radius: 1rem;
}
input, select{
width: 100%;
@apply focus:outline focus:outline-myAccent focus:outline-2;
pre {
text-wrap: wrap;
}
input::placeholder{
color: var(--text);
opacity: 1;
input,
select {
width: 100%;
@apply focus:outline focus:outline-myAccent focus:outline-2;
}
input::-ms-input-placeholder { /* Edge 12 -18 */
color: var(--text);
}
svg{
height: 90%;
margin: auto 0;
width: auto;
span {
@apply focus-visible:outline focus-visible:outline-myAccent focus-visible:outline-2;
}
*{
color: var(--text);
input::placeholder {
color: var(--placeholder);
opacity: 1;
}
button{
background-color: var(--accent);
border-radius: 1rem;
color: var(--secondary);
font-weight: bold;
min-width: 4rem;
input::-ms-input-placeholder {
/* Edge 12 -18 */
color: var(--placeholder);
}
.randomButton{
@apply flex justify-center content-center p-1;
svg {
height: 90%;
margin: auto 0;
width: auto;
}
* {
color: var(--text);
}
button {
background-color: var(--accent);
border-radius: 1rem;
color: var(--secondary);
font-weight: bold;
min-width: 4rem;
}
button path {
color: var(--secondary);
color: var(--secondary);
}
.property {
background-color: #9D9D9D;
border-radius: .5rem;
/* padding: .5rem; */
display: inline-flex;
flex-direction: column;
gap: .25rem;
/* background-color: #9d9d9d; */
border-radius: 0.5rem;
/* padding: .5rem; */
display: inline-flex;
flex-direction: column;
gap: 0.25rem;
}
.property p, .property h3{
align-self: left;
padding-left: .5rem;
.property p,
.property h3 {
align-self: left;
padding-left: 0.5rem;
}
.property > div {
width: 100%;
div.property {
width: 100%;
}
.property span {
background: #B3B3B3;
padding: .25rem .75rem;
border-radius: 1rem;
/* width: 100%; */
}
background: var(--input_bg);
padding: 0.25rem 0.75rem;
border-radius: 1rem;
/* width: 100%; */
}
#entityPlaceholder {
background: transparent;
border: 2px dotted var(--input_bg);
color: var(--input_bg);
font-weight: normal;
}
#entityPlaceholder:hover {
border-color: var(--secondary);
color: var(--secondary);
}