M5-T12: add expose toggle API and Home Assistant Expose settings panel

This commit is contained in:
2026-06-22 15:50:49 +02:00
parent 35b95f5c88
commit 45d87f36f7
11 changed files with 2420 additions and 3 deletions
+344 -2
View File
@@ -39,6 +39,7 @@ export interface paths {
*
* - Blank secret value keeps the existing stored value (no change).
* - Invalid values return 422 and nothing is written to the database.
* - If MQTT-related settings changed, the MQTT client reconnects automatically.
*/
put: operations["put_config_api_config_put"];
post?: never;
@@ -76,6 +77,37 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/config/mqtt/test": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Post Mqtt Test
* @description Test MQTT broker connectivity by attempting to connect and publishing a
* test message to ``<ha_discovery_prefix>/home-automation/test``.
*
* The message is visible in MQTT Explorer (or any subscriber) so users can
* confirm the full broker publish path is working.
*
* Three possible outcomes:
* - 200 { "result": "success", "message": ... }
* - 400 { "result": "config-error", "message": ... } (not configured)
* - 502 { "result": "failed", "message": ... } (connection/publish error)
*
* MQTT credentials are never echoed in the response.
*/
post: operations["post_mqtt_test_api_config_mqtt_test_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/locations": {
parameters: {
query?: never;
@@ -210,6 +242,66 @@ export interface paths {
patch: operations["patch_poo_api_poo__timestamp__patch"];
trace?: never;
};
"/api/expose": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Expose
* @description Return the full exposable-entity catalog with toggle states and MQTT status.
*
* The catalog is computed dynamically from registered providers (e.g. the
* Modbus provider enumerates all enabled devices and their metric entities).
* Toggle states come from the ``exposed_entity_toggle`` table; entities with
* no row default to ``enabled=False``.
*/
get: operations["get_expose_api_expose_get"];
/**
* Put Expose
* @description Set per-entity toggle state.
*
* Accepts a map of ``{key: bool}`` and upserts rows in the
* ``exposed_entity_toggle`` table. Only keys present in ``body.toggles``
* are touched; other entities' toggles are left unchanged.
*
* After writing the toggles, triggers a HA Discovery re-publish so any
* changes (enabled ↔ disabled) are reflected in Home Assistant immediately.
*/
put: operations["put_expose_api_expose_put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/expose/republish": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Post Expose Republish
* @description Manually trigger a full HA Discovery re-publish.
*
* Calls ``publish_discovery(session)`` from the HA discovery service (M5-T11).
* Returns a status indicating whether the publish was attempted (or skipped
* because MQTT / discovery is not enabled / connected).
*/
post: operations["post_expose_republish_api_expose_republish_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/modbus/profiles": {
parameters: {
query?: never;
@@ -332,8 +424,16 @@ export interface paths {
* Get Readings
* @description Return time-range readings for a device.
*
* Results are ordered by ``recorded_at`` ascending and capped by ``limit``
* (max {_READINGS_LIMIT_MAX}) to prevent accidental full-table exports.
* When the window contains more rows than ``limit``, the **most recent** N rows
* are returned (``ORDER BY recorded_at DESC LIMIT n``), then reversed to
* ascending order before being sent to the client. This ensures that for long
* time-range requests (e.g. 6 h / 24 h) the caller always sees the latest data
* rather than the oldest segment of the window.
*
* When the window has fewer rows than ``limit`` the full window is returned in
* ascending order — behaviour is identical to a plain ascending query.
*
* The response schema is unchanged: items are always ``recorded_at`` ascending.
*
* The query uses the ``(device_id, recorded_at)`` composite index for
* efficient time-window scans.
@@ -723,6 +823,15 @@ export interface paths {
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/**
* CatalogEntrySchema
* @description An entity from the catalog with its current toggle state.
*/
CatalogEntrySchema: {
entity: components["schemas"]["ExposableEntitySchema"];
/** Enabled */
enabled: boolean;
};
/** ConfigField */
ConfigField: {
/** Env Name */
@@ -765,6 +874,69 @@ export interface components {
/** Sections */
sections: components["schemas"]["ConfigSection"][];
};
/**
* DeviceInfoSchema
* @description HA device grouping info for an exposable entity.
*/
DeviceInfoSchema: {
/** Identifiers */
identifiers: string[];
/** Name */
name: string;
};
/**
* ExposableEntitySchema
* @description One exposable entity in the catalog.
*
* ``value_getter`` is intentionally excluded — it is a non-serialisable
* callable and is only used internally by the HA Discovery service.
*/
ExposableEntitySchema: {
/** Key */
key: string;
/** Component */
component: string;
device: components["schemas"]["DeviceInfoSchema"];
/** Device Class */
device_class: string | null;
/** Unit */
unit: string;
/** Name */
name: string;
/** State Class */
state_class?: string | null;
};
/**
* ExposeResponse
* @description Response for GET /api/expose.
*/
ExposeResponse: {
/** Catalog */
catalog: components["schemas"]["CatalogEntrySchema"][];
mqtt_status: components["schemas"]["MqttStatusSchema"];
};
/**
* ExposeUpdateRequest
* @description Request body for PUT /api/expose.
*
* ``toggles`` is a map from entity key to desired enabled state (bool).
* Only keys present in the map are updated; absent keys are untouched.
*/
ExposeUpdateRequest: {
/** Toggles */
toggles: {
[key: string]: boolean;
};
};
/**
* ExposeUpdateResponse
* @description Response for PUT /api/expose (returns updated catalog + status).
*/
ExposeUpdateResponse: {
/** Catalog */
catalog: components["schemas"]["CatalogEntrySchema"][];
mqtt_status: components["schemas"]["MqttStatusSchema"];
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
@@ -1009,6 +1181,31 @@ export interface components {
/** Error */
error?: string | null;
};
/**
* MqttStatusSchema
* @description Connection status for MQTT and HA Discovery.
*/
MqttStatusSchema: {
/** Mqtt Configured */
mqtt_configured: boolean;
/** Mqtt Connected */
mqtt_connected: boolean;
/** Discovery Enabled */
discovery_enabled: boolean;
};
/**
* MqttTestResponse
* @description Response from POST /api/config/mqtt/test.
*/
MqttTestResponse: {
/**
* Result
* @enum {string}
*/
result: "success" | "config-error" | "failed";
/** Message */
message: string;
};
/** PasswordChangeRequest */
PasswordChangeRequest: {
/** Current Password */
@@ -1124,6 +1321,16 @@ export interface components {
/** Last Provider */
last_provider: string | null;
};
/**
* RepublishResponse
* @description Response for POST /api/expose/republish.
*/
RepublishResponse: {
/** Ok */
ok: boolean;
/** Message */
message: string;
};
/** SessionResponse */
SessionResponse: {
user: components["schemas"]["SessionUser"];
@@ -1341,6 +1548,55 @@ export interface operations {
};
};
};
post_mqtt_test_api_config_mqtt_test_post: {
parameters: {
query?: never;
header?: {
"X-CSRF-Token"?: string | null;
};
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MqttTestResponse"];
};
};
/** @description Bad Request */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MqttTestResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
/** @description Bad Gateway */
502: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MqttTestResponse"];
};
};
};
};
get_locations_api_locations_get: {
parameters: {
query?: {
@@ -1576,6 +1832,92 @@ export interface operations {
};
};
};
get_expose_api_expose_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExposeResponse"];
};
};
};
};
put_expose_api_expose_put: {
parameters: {
query?: never;
header?: {
"X-CSRF-Token"?: string | null;
};
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ExposeUpdateRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ExposeUpdateResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
post_expose_republish_api_expose_republish_post: {
parameters: {
query?: never;
header?: {
"X-CSRF-Token"?: string | null;
};
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["RepublishResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_profiles_api_modbus_profiles_get: {
parameters: {
query?: never;