This commit is contained in:
2025-07-30 13:02:56 +02:00
34 changed files with 1254 additions and 396 deletions

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,6 +1,34 @@
# MqttCreator
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.4.
This project is a simple and incomplete generator to create a Homeassinstant compatible MQTT Autodiscovery Entity
## Limited Coverage
By now only the these entities (binary_sensor, light, sensor, switch) are supported by the generator, because they are the most commmonly used entities for my own needs
## Usage
The UI is seperated into two parts, the global generator with settings and upper Topic and the entity itselt
### Generator:
Automated Topic Settings:
- all: the topics will be generated automatically from the other supplied inputs, all changes will be wiped
- only changes: user inputs will be synced accross all topics (only works if the general structure stays the same and changes are not to large at once)
- none: everything is done manually
### Entity:
After choosing an entity type, change and input the corresponding values as you like and if automated topics is actived the topics will be populated automatically
Once done, you can press the DiscoveryString button.
### Discovery String:
First you see the discovery string formatted, there you can check for any errors in the configuration
Below there will be a discovery string with escaped perentecies to input into print statements or similar
The last field Discovery Topic is the automatically generated Discovery Topic for default Homeassistant MQTT Autodiscovery
<!-- This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.4.
## Development server
@@ -24,4 +52,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. -->

7
docker-compose.yaml Normal file
View File

@@ -0,0 +1,7 @@
services:
mqtt_creator:
image: git.letsstein.de/tom/mqtt_creator:latest
restart: unless-stopped
ports:
- "${PORT}:80"

View File

@@ -1,7 +1,54 @@
import { DEVICE_CLASS, MQTTEntity } from "./mqtt_base";
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
export class MqttBinary extends MQTTEntity {
dev_cla: DEVICE_CLASS = 0
pl_on: string = "1";
pl_off: string = "0";
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',
'cold',
'connectivity',
'door',
'garage_door',
'gas',
'heat',
'light',
'lock',
'moisture',
'motion',
'moving',
'occupancy',
'opening',
'plug',
'power',
'presence',
'problem',
'running',
'safety',
'smoke',
'sound',
'tamper',
'update',
'vibration',
'window',
]);
pl_on: string = '1';
pl_off: string = '0';
}

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";
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";
val_tpl: string = "";
}
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 { DEVICE_CLASS, MQTTEntity } from "./mqtt_base";
import { DeviceClass, iMQTTEntityBase, MQTTEntity } from './mqtt_base';
export class MqttSwitch extends MQTTEntity {
dev_cla: DEVICE_CLASS = 0
cmd_t: string = "command/topic";
pl_on: string = "1";
pl_off: string = "0";
}
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 = '';
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,43 +1,147 @@
export class MQTTEntity {
name: string = "";
stat_t: string = "state/topic";
uniq_id: string = "unique_id";
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;
createString() : string{
let string: string = "";
for(let property of Object.getOwnPropertyNames(this)){
if(this[property as keyof this] == null) continue;
string += `"${property}": "${this[property as keyof this]}",`
}
console.log(string);
return string;
attrs = new Set(['name', 'stat_t', 'uniq_id']);
topic_updates = new EventEmitter<string>();
readonly ent_type: string = 'base';
get name() {
return this._name;
}
set name(name: string) {
this._name = name;
if (name == '') return;
this._uniq_id = name + '_' + hash(name);
this.topic_updates.next('stat_t');
}
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);
}
return jsonObject;
}
get size(): number {
this._size = JSON.stringify(this.toJSON()).length;
return this._size;
}
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 MQTTDevice {
name: string = "";
identifiers: string[] = ["MQTT"];
serial_number: string = "";
configuration_url: string = "";
export class DeviceClass {
value: number = 0;
choices: string[] = ['None'];
constructor(choices: string[]) {
this.choices = choices;
}
toString(): string {
return this.choices[this.value];
}
}
// export enum ENTITY_TYPE {
// light = 0,
// switch = 1,
// sensor = 2,
// binary_sensor = 3,
// button = 4,
// }
export interface iMQTTEntityBase {
name: string;
stat_t: string;
uniq_id: string;
display_name: string;
export enum DEVICE_CLASS {
motion = 0,
movement = 1,
outlet = 2
}
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,50 +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 = "";
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

@@ -1,3 +1,11 @@
<div>
<router-outlet></router-outlet>
</div>
<!DOCTYPE html>
<main class="p-4">
<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.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>
<router-outlet></router-outlet>
</main>

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,27 +0,0 @@
.property {
background-color: #9D9D9D;
border-radius: .5rem;
padding: .5rem;
display: inline-flex;
flex-direction: column;
column-gap: 1rem;
}
.property p{
align-self: center;
}
.property > div {
width: 100%;
}
.property span {
background: #B3B3B3;
padding: .25rem .75rem;
border-radius: 1rem;
/* width: 100%; */
}
.buttonOff path{
fill:#535353;
}

View File

@@ -1,81 +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 placeholder="Name here" type="text"[ngModel]="entity_name" (ngModelChange)="entity_name = $event; updateStateTopic()"/>
</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; updateStateTopic()"/>
<button (click)="entity_uniq_id = useRandomString(10); updateStateTopic()" 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" (ngModelChange)="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)="entity_bri_cmd_t = $event"/>
</div>
<div class="property !flex-row" >
<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>
<input type="text" [ngModel]="entity_dev_cla" (ngModelChange)="entity_dev_cla = $event"/>
</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]="state_topic" (change)="lockStateTopic($event)"/>
<label class="randomButton" [ngClass]="auto_stat_t ? 'bg-mySecondary buttonOff' : ''">
<input type="checkbox" class="hidden" [ngModel]="auto_stat_t" (ngModelChange)="auto_stat_t = $event; updateStateTopic()"/>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="fill-myButton" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"/>
</svg>
</label>
</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>
<span contenteditable>{{discoveryString}}</span>
</div>
<div class="col-span-2 property" >
<p>Discovery Topic</p>
<input type="text" readonly value="{{discoveryTopic}}">
</div>
</ng-container>
</main>

View File

@@ -1,98 +0,0 @@
import { Component, Input } from '@angular/core';
import { GeneratorService, entity_types, randomString } from '../_services/generator.service';
import { MqttBinary } from '../_models/mqtt-binary';
import { 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.updateStateTopic();
})
}
readonly useObject = Object;
readonly entities = entity_types;
readonly useRandomString = randomString;
auto_stat_t: boolean = true;
showDiscovery: boolean = false;
@Input() entity_type: number = 0;
@Input() state_topic: 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 = "";
@Input() entity_pl_on: string = "";
@Input() entity_unit_of_meas: string = "";
@Input() entity_val_tpl: string = "";
@Input() entity_dev_cla: string = "";
updateStateTopic() {
if (!this.auto_stat_t) return
this.state_topic = ""
if (this.generatorService.upperTopic != "") this.state_topic += this.generatorService.upperTopic + "/";
if (this.entity_type != 0) this.state_topic += entity_types[this.entity_type][0] + "/"
if (this.generatorService.device_name != "") this.state_topic += this.generatorService.device_name + "/"
if (this.entity_name != "" && this.entity_uniq_id != "") this.state_topic += this.entity_name + "_" + this.entity_uniq_id + "/stat"
else if (this.entity_name != "") this.state_topic += this.entity_name + "/stat"
this.state_topic = this.state_topic.toLocaleLowerCase();
}
basemodelProperty(property: string) {
if (this.basemodel?.hasOwnProperty(property)) console.log(property)
return this.basemodel?.stat_t
}
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
} else this.generatorService.selected_entity = null
this.entity_type = event as number;
this.updateStateTopic();
}
lockStateTopic(event: any) {
this.auto_stat_t = false;
}
create_entity() {
this.basemodel?.setProperty('stat_t', this.state_topic);
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.showDiscovery = true;
return
}
get discoveryString() {
let discString = this.basemodel?.createString()
if (discString == "" || discString == undefined) return "";
discString = discString.replaceAll('"', '\\"')
return "{" + discString + "}"
}
get discoveryTopic() {
if (this.entity_type == 0) return "";
if (this.entity_name == "") return "";
let discTopic = "homeassistant/" + entity_types[this.entity_type][0] + "/" + this.entity_name + "_" + this.entity_uniq_id + "/config"
return discTopic;
}
}

View File

@@ -1,5 +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 ;
}
grid-column: 1;
position: relative;
}
.outContainer {
display: flex;
flex-direction: column;
grid-column: 2;
}
.customCheckboxContainer {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.customCheckboxContainer input {
display: none;
}
.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,31 +1,74 @@
<main class="flex flex-col gap-2 p-4">
<div class="flex flex-row justify-left gap-4 items-center h-12">
<img src="mqtt_creator.ico" alt="app icon" class="h-full">
<h2 class="text-3xl font-bold text-myButton" >MQTT Discovery Creator</h2>
</div>
<!-- <div class="border border-slate-800 my-2">
<h2>Device: - not working</h2>
<p>Name</p><input type="text" placeholder="name here" [ngModel]="generatorService.device_name"
(ngModelChange)="generatorService.device_name = $event; generatorService.update()">
<p>Identifier</p>
<input type="text" placeholder="Identifier here" [ngModel]="generatorService.device_id"
(ngModelChange)="generatorService.device_id = $event">
<button (click)="generatorService.device_id = useRandomString(16)">Random</button>
<p>Standalone -> no device</p>
<input type="checkbox" [ngModel]="generatorService.device_standalone"
(ngModelChange)="generatorService.device_standalone = $event; generatorService.update()" />
</div> -->
<div class="genContainer">
<!DOCTYPE html>
<div id="mainContainer" class="" [ngClass]="{'outEnable':outputService.output}">
<div class="genContainer grid grid-cols-2 gap-4">
<!-- Statetopic -->
<h3>Data Channel</h3>
<div>
<p>Bereich</p>
<input type="text" [ngModel]="generatorService.upperTopic"
(ngModelChange)="generatorService.upperTopic = $event; generatorService.update()" />
<!-- <h3>Data Channel</h3> -->
<div class="property col-span-2">
<p>Bereich</p>
<input type="text" [ngModel]="generatorService.upper_topic"
(ngModelChange)="generatorService.upper_topic = $event" />
</div>
<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>
</main>
</div>
<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,33 +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;
@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 codeSpan(): string {
let start = this.generatorService.selected_entity == null ? 3 : 2;
return `${start}/${3 + this.generatorService.created_entity_num}`;
}
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,45 +3,105 @@
@tailwind components;
@tailwind utilities;
input, select {
background: #B3B3B3;
padding: .25rem .75rem;
border-radius: 1rem;
width: 100%;
@apply focus:outline focus:outline-myAccent focus:outline-2;
:root {
--accent: #4cb926;
--text: #3d3d3d;
--primary: #b3b3b3;
--secondary: #f8f8f8;
--placeholder: #777;
--input_bg: #b3b3b3;
}
input::placeholder{
color: #535353;
opacity: 1;
input,
select,
label,
pre,
button {
background: var(--primary);
padding: 0.25rem 0.75rem;
border-radius: 1rem;
}
input::-ms-input-placeholder { /* Edge 12 -18 */
color: #535353;
}
svg{
height: 90%;
margin: auto 0;
width: auto;
pre {
text-wrap: wrap;
}
*{
color: #535353;
input,
select {
width: 100%;
@apply focus:outline focus:outline-myAccent focus:outline-2;
}
button, label{
background-color: #4CB926;
border-radius: 1rem;
color: #F8F8F8;
font-weight: bold;
min-width: 4rem;
span {
@apply focus-visible:outline focus-visible:outline-myAccent focus-visible:outline-2;
}
.randomButton{
@apply flex justify-center content-center p-1;
input::placeholder {
color: var(--placeholder);
opacity: 1;
}
input::-ms-input-placeholder {
/* Edge 12 -18 */
color: var(--placeholder);
}
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: #F8F8F8;
}
color: var(--secondary);
}
.property {
/* 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: 0.5rem;
}
div.property {
width: 100%;
}
.property span {
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);
}