simo 2.11.3__py3-none-any.whl → 3.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simo might be problematic. Click here for more details.
- simo/__pycache__/settings.cpython-312.pyc +0 -0
- simo/asgi.py +25 -6
- simo/automation/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/automation/controllers.py +18 -2
- simo/automation/forms.py +15 -24
- simo/backups/rescue.img.xz +0 -0
- simo/core/__pycache__/admin.cpython-312.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/core/__pycache__/forms.cpython-312.pyc +0 -0
- simo/core/__pycache__/models.cpython-312.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-312.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/core/admin.py +5 -4
- simo/core/base_types.py +191 -18
- simo/core/controllers.py +259 -26
- simo/core/forms.py +10 -2
- simo/core/management/_hub_template/hub/nginx.conf +23 -50
- simo/core/management/_hub_template/hub/supervisor.conf +15 -0
- simo/core/mcp.py +154 -0
- simo/core/migrations/0051_instance_ai_memory.py +18 -0
- simo/core/migrations/__pycache__/0051_instance_ai_memory.cpython-312.pyc +0 -0
- simo/core/models.py +3 -0
- simo/core/serializers.py +120 -0
- simo/core/signal_receivers.py +1 -1
- simo/core/tasks.py +1 -3
- simo/core/utils/__pycache__/type_constants.cpython-312.pyc +0 -0
- simo/core/utils/type_constants.py +78 -17
- simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/api.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/serializers.cpython-312.pyc +0 -0
- simo/fleet/admin.py +5 -1
- simo/fleet/api.py +2 -27
- simo/fleet/base_types.py +35 -4
- simo/fleet/controllers.py +150 -156
- simo/fleet/forms.py +56 -88
- simo/fleet/gateways.py +8 -15
- simo/fleet/migrations/0055_colonel_is_vo_active_colonel_last_wake_and_more.py +28 -0
- simo/fleet/migrations/0056_delete_customdalidevice.py +16 -0
- simo/fleet/migrations/__pycache__/0055_colonel_is_vo_active_colonel_last_wake_and_more.cpython-312.pyc +0 -0
- simo/fleet/migrations/__pycache__/0056_delete_customdalidevice.cpython-312.pyc +0 -0
- simo/fleet/models.py +13 -72
- simo/fleet/serializers.py +1 -48
- simo/fleet/socket_consumers.py +100 -39
- simo/fleet/tasks.py +2 -22
- simo/fleet/voice_assistant.py +893 -0
- simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/generic/base_types.py +70 -10
- simo/generic/controllers.py +102 -15
- simo/generic/gateways.py +10 -10
- simo/mcp_server/__init__.py +0 -0
- simo/mcp_server/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/admin.cpython-312.pyc +0 -0
- simo/mcp_server/__pycache__/models.cpython-312.pyc +0 -0
- simo/mcp_server/admin.py +18 -0
- simo/mcp_server/app.py +4 -0
- simo/mcp_server/auth.py +34 -0
- simo/mcp_server/dummy.py +22 -0
- simo/mcp_server/migrations/0001_initial.py +30 -0
- simo/mcp_server/migrations/0002_alter_instanceaccesstoken_date_expired.py +18 -0
- simo/mcp_server/migrations/0003_instanceaccesstoken_issuer.py +18 -0
- simo/mcp_server/migrations/__init__.py +0 -0
- simo/mcp_server/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0002_alter_instanceaccesstoken_date_expired.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/0003_instanceaccesstoken_issuer.cpython-312.pyc +0 -0
- simo/mcp_server/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
- simo/mcp_server/models.py +27 -0
- simo/mcp_server/server.py +60 -0
- simo/mcp_server/tasks.py +19 -0
- simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
- simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/multimedia/base_types.py +29 -4
- simo/multimedia/controllers.py +66 -19
- simo/settings.py +1 -0
- simo/users/__pycache__/utils.cpython-312.pyc +0 -0
- simo/users/utils.py +10 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/METADATA +12 -4
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/RECORD +90 -64
- simo/fleet/custom_dali_operations.py +0 -287
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/WHEEL +0 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/entry_points.txt +0 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/licenses/LICENSE.md +0 -0
- {simo-2.11.3.dist-info → simo-3.0.1.dist-info}/top_level.txt +0 -0
|
Binary file
|
simo/asgi.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import importlib, datetime, sys
|
|
1
|
+
import importlib, importlib.util, datetime, sys, traceback
|
|
2
2
|
from django.core.asgi import get_asgi_application
|
|
3
3
|
from channels.auth import AuthMiddlewareStack
|
|
4
4
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
@@ -13,13 +13,32 @@ for name, app in apps.app_configs.items():
|
|
|
13
13
|
'staticfiles'
|
|
14
14
|
):
|
|
15
15
|
continue
|
|
16
|
+
|
|
17
|
+
module_path = f"{app.name}.routing"
|
|
18
|
+
|
|
19
|
+
# Only attempt import if the module exists
|
|
20
|
+
spec = importlib.util.find_spec(module_path)
|
|
21
|
+
if spec is None:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
# Import and collect urlpatterns; on failure, print full traceback and continue
|
|
25
|
+
try:
|
|
26
|
+
routing = importlib.import_module(module_path)
|
|
27
|
+
except Exception:
|
|
28
|
+
print(
|
|
29
|
+
f"Failed to import {module_path}:\n{traceback.format_exc()}"
|
|
30
|
+
)
|
|
31
|
+
continue
|
|
32
|
+
|
|
16
33
|
try:
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
app_urlpatterns = getattr(routing, 'urlpatterns', None)
|
|
35
|
+
if isinstance(app_urlpatterns, list):
|
|
36
|
+
urlpatterns.extend(app_urlpatterns)
|
|
37
|
+
except Exception:
|
|
38
|
+
print(
|
|
39
|
+
f"Error while processing urlpatterns from {module_path}:\n{traceback.format_exc()}"
|
|
40
|
+
)
|
|
19
41
|
continue
|
|
20
|
-
for var_name, item in routing.__dict__.items():
|
|
21
|
-
if isinstance(item, list) and var_name == 'urlpatterns':
|
|
22
|
-
urlpatterns.extend(item)
|
|
23
42
|
|
|
24
43
|
|
|
25
44
|
class TimestampedStream:
|
|
Binary file
|
simo/automation/controllers.py
CHANGED
|
@@ -63,24 +63,38 @@ class Script(ControllerBase, TimerMixin):
|
|
|
63
63
|
return 'stopped'
|
|
64
64
|
|
|
65
65
|
def start(self, new_code=None):
|
|
66
|
+
"""Start the script process (optionally updating source code).
|
|
67
|
+
|
|
68
|
+
Parameters:
|
|
69
|
+
- new_code (str|None): Optional Python script to persist before start.
|
|
70
|
+
"""
|
|
66
71
|
if new_code:
|
|
67
72
|
self.component.new_code = new_code
|
|
68
73
|
self.send('start')
|
|
69
74
|
|
|
70
75
|
def play(self):
|
|
76
|
+
"""Alias for `start()` to harmonize with media-like controls."""
|
|
71
77
|
return self.start()
|
|
72
78
|
|
|
73
79
|
def stop(self):
|
|
80
|
+
"""Stop the running script process."""
|
|
74
81
|
self.send('stop')
|
|
75
82
|
|
|
76
83
|
def toggle(self):
|
|
84
|
+
"""Toggle script run state between running and stopped."""
|
|
77
85
|
self.component.refresh_from_db()
|
|
78
86
|
if self.component.value == 'running':
|
|
79
87
|
self.send('stop')
|
|
80
88
|
else:
|
|
81
89
|
self.send('start')
|
|
82
90
|
|
|
83
|
-
def ai_assistant(self, wish):
|
|
91
|
+
def ai_assistant(self, wish, current_code=None):
|
|
92
|
+
"""Request an AI-generated script for the given natural-language wish.
|
|
93
|
+
|
|
94
|
+
Parameters:
|
|
95
|
+
- wish (str): User intent in natural language.
|
|
96
|
+
Returns: dict with status, generated script, and description.
|
|
97
|
+
"""
|
|
84
98
|
try:
|
|
85
99
|
request_data = {
|
|
86
100
|
'hub_uid': dynamic_settings['core__hub_uid'],
|
|
@@ -88,6 +102,7 @@ class Script(ControllerBase, TimerMixin):
|
|
|
88
102
|
'instance_uid': get_current_instance().uid,
|
|
89
103
|
'system_data': json.dumps(get_current_state()),
|
|
90
104
|
'wish': wish,
|
|
105
|
+
'current_code': current_code
|
|
91
106
|
}
|
|
92
107
|
except Exception as e:
|
|
93
108
|
print(traceback.format_exc(), file=sys.stderr)
|
|
@@ -97,7 +112,7 @@ class Script(ControllerBase, TimerMixin):
|
|
|
97
112
|
request_data['current_user'] = UserSerializer(user, many=False).data
|
|
98
113
|
try:
|
|
99
114
|
response = requests.post(
|
|
100
|
-
'https://simo.io/
|
|
115
|
+
'https://simo.io/ai/scripts/', json=request_data
|
|
101
116
|
)
|
|
102
117
|
except:
|
|
103
118
|
return {'status': 'error', 'result': "Connection error"}
|
|
@@ -121,6 +136,7 @@ class PresenceLighting(Script):
|
|
|
121
136
|
masters_only = False
|
|
122
137
|
name = _("Presence lighting")
|
|
123
138
|
config_form = PresenceLightingConfigForm
|
|
139
|
+
accepts_value = False
|
|
124
140
|
|
|
125
141
|
def __init__(self, *args, **kwargs):
|
|
126
142
|
super().__init__(*args, **kwargs)
|
simo/automation/forms.py
CHANGED
|
@@ -33,13 +33,12 @@ class ScriptConfigForm(BaseComponentForm):
|
|
|
33
33
|
"in my living room when it get's dark."
|
|
34
34
|
}
|
|
35
35
|
),
|
|
36
|
-
help_text="
|
|
37
|
-
"you want to happen with this scenario script. <br>"
|
|
38
|
-
"The more defined, exact and clear is your description the more "
|
|
36
|
+
help_text="The more defined, exact and clear is your description the more "
|
|
39
37
|
"accurate automation script SIMO.io AI assistanw will generate.<br>"
|
|
40
|
-
"Use component, zone and category
|
|
41
|
-
"SIMO.io AI will re-generate your automation code and update it's description
|
|
42
|
-
"every time
|
|
38
|
+
"Use component, zone and category ID's for best accuracy. <br>"
|
|
39
|
+
"SIMO.io AI will re-generate your automation code and update it's description "
|
|
40
|
+
"every time you enter something in this field. <br>"
|
|
41
|
+
"Takes up to 60s to do it. <br>"
|
|
43
42
|
"Actual script code can only be edited via SIMO.io Admin.",
|
|
44
43
|
)
|
|
45
44
|
code = forms.CharField(widget=PythonCode, required=False)
|
|
@@ -85,24 +84,16 @@ class ScriptConfigForm(BaseComponentForm):
|
|
|
85
84
|
|
|
86
85
|
|
|
87
86
|
def clean(self):
|
|
88
|
-
if self.cleaned_data
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
resp = self.instance.ai_assistant(
|
|
99
|
-
self.cleaned_data['assistant_request'],
|
|
100
|
-
)
|
|
101
|
-
if resp['status'] == 'success':
|
|
102
|
-
self._ai_resp = resp
|
|
103
|
-
elif resp['status'] == 'error':
|
|
104
|
-
self.add_error('assistant_request', resp['result'])
|
|
105
|
-
|
|
87
|
+
if self.cleaned_data['assistant_request']:
|
|
88
|
+
resp = self.instance.ai_assistant(
|
|
89
|
+
self.cleaned_data['assistant_request'],
|
|
90
|
+
self.instance.config.get('code')
|
|
91
|
+
)
|
|
92
|
+
if resp['status'] == 'success':
|
|
93
|
+
self._ai_resp = resp
|
|
94
|
+
self.cleaned_data['assistant_request'] = None
|
|
95
|
+
elif resp['status'] == 'error':
|
|
96
|
+
self.add_error('assistant_request', resp['result'])
|
|
106
97
|
return self.cleaned_data
|
|
107
98
|
|
|
108
99
|
def save(self, commit=True):
|
simo/backups/rescue.img.xz
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/admin.py
CHANGED
|
@@ -352,9 +352,10 @@ class ComponentAdmin(EasyObjectsDeleteMixin, admin.ModelAdmin):
|
|
|
352
352
|
|
|
353
353
|
ctx['is_last'] = True
|
|
354
354
|
ctx['current_step'] = 3
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
)
|
|
355
|
+
# Normalize controller base type to slug for display
|
|
356
|
+
bt = getattr(controller_cls, 'base_type', None)
|
|
357
|
+
slug = bt if isinstance(bt, str) else getattr(bt, 'slug', None)
|
|
358
|
+
ctx['selected_type'] = ALL_BASE_TYPES.get(slug or bt, slug or bt)
|
|
358
359
|
ctx['info'] = controller_cls.info(controller_cls)
|
|
359
360
|
if request.method == 'POST':
|
|
360
361
|
ctx['form'] = add_form(
|
|
@@ -506,4 +507,4 @@ class ComponentAdmin(EasyObjectsDeleteMixin, admin.ModelAdmin):
|
|
|
506
507
|
'value_history': obj.history.filter(type='value').order_by('-date')[:50],
|
|
507
508
|
'arm_status_history': obj.history.filter(type='security').order_by('-date')[:50]
|
|
508
509
|
}
|
|
509
|
-
)
|
|
510
|
+
)
|
simo/core/base_types.py
CHANGED
|
@@ -1,23 +1,196 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Component base types as first-class, self-describing classes.
|
|
3
|
+
|
|
4
|
+
Apps can define their base types in their own `<app>.base_types` module
|
|
5
|
+
by subclassing `BaseComponentType`. For backward compatibility, each
|
|
6
|
+
module should also export a `BASE_TYPES` mapping `{slug: name}`; this
|
|
7
|
+
file exports such mapping automatically from declared classes.
|
|
3
8
|
"""
|
|
4
9
|
|
|
10
|
+
from abc import ABC
|
|
5
11
|
from django.utils.translation import gettext_lazy as _
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
|
|
13
|
+
|
|
14
|
+
class BaseComponentType(ABC):
|
|
15
|
+
"""Abstract base for component base types.
|
|
16
|
+
|
|
17
|
+
Subclasses should set:
|
|
18
|
+
- slug: string identifier stored in Component.base_type
|
|
19
|
+
- name: human-friendly name (lazy-translated)
|
|
20
|
+
- description: short explanation of what this type represents
|
|
21
|
+
- purpose: when/why to use this type
|
|
22
|
+
- required_methods: tuple of controller method names that must be
|
|
23
|
+
implemented by controllers of this base type (optional).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
slug: str = ''
|
|
27
|
+
name: str = ''
|
|
28
|
+
description: str = ''
|
|
29
|
+
purpose: str = ''
|
|
30
|
+
required_methods: tuple = tuple()
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def describe(cls) -> dict:
|
|
34
|
+
return {
|
|
35
|
+
'slug': cls.slug,
|
|
36
|
+
'name': cls.name,
|
|
37
|
+
'description': cls.description,
|
|
38
|
+
'purpose': cls.purpose,
|
|
39
|
+
'required_methods': list(cls.required_methods or ()),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def validate_controller(cls, controller_cls):
|
|
44
|
+
"""Validate that a controller satisfies this type's contract.
|
|
45
|
+
|
|
46
|
+
Raises TypeError with a helpful message on mismatch.
|
|
47
|
+
"""
|
|
48
|
+
# If there are no explicit requirements, nothing to validate.
|
|
49
|
+
if not cls.required_methods:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
missing = []
|
|
53
|
+
for method in cls.required_methods:
|
|
54
|
+
attr = getattr(controller_cls, method, None)
|
|
55
|
+
if not callable(attr):
|
|
56
|
+
missing.append(method)
|
|
57
|
+
if missing:
|
|
58
|
+
reqs = ', '.join(cls.required_methods)
|
|
59
|
+
raise TypeError(
|
|
60
|
+
f"Controller {controller_cls.__module__}.{controller_cls.__name__} "
|
|
61
|
+
f"for base type '{cls.slug}' is missing required method(s): "
|
|
62
|
+
f"{', '.join(missing)}. Expected: {reqs}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---- Core base types -------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NumericSensorType(BaseComponentType):
|
|
70
|
+
slug = 'numeric-sensor'
|
|
71
|
+
name = _("Numeric sensor")
|
|
72
|
+
description = _("Represents a single numeric value that changes over time.")
|
|
73
|
+
purpose = _("Use for temperature, humidity, light level, etc.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MultiSensorType(BaseComponentType):
|
|
77
|
+
slug = 'multi-sensor'
|
|
78
|
+
name = _("Multi sensor")
|
|
79
|
+
description = _("Represents several labeled readings in one component.")
|
|
80
|
+
purpose = _("Use when a single device reports multiple values.")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class BinarySensorType(BaseComponentType):
|
|
84
|
+
slug = 'binary-sensor'
|
|
85
|
+
name = _("Binary sensor")
|
|
86
|
+
description = _("A boolean on/off style sensor.")
|
|
87
|
+
purpose = _("Use for motion, door, presence, and similar states.")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ButtonType(BaseComponentType):
|
|
91
|
+
slug = 'button'
|
|
92
|
+
name = _("Button")
|
|
93
|
+
description = _("Momentary button events like click, double-click, hold.")
|
|
94
|
+
purpose = _("Use to model input-only button devices.")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SwitchType(BaseComponentType):
|
|
98
|
+
slug = 'switch'
|
|
99
|
+
name = _("Switch")
|
|
100
|
+
description = _("Binary on/off actuator.")
|
|
101
|
+
purpose = _("Use to control relays, power sockets, or generic toggles.")
|
|
102
|
+
required_methods = ('turn_on', 'turn_off', 'toggle')
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class DoubleSwitchType(BaseComponentType):
|
|
106
|
+
slug = 'switch-double'
|
|
107
|
+
name = _("Switch Double")
|
|
108
|
+
description = _("Two-channel on/off actuator.")
|
|
109
|
+
purpose = _("Use to control two separate loads in one device.")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TripleSwitchType(BaseComponentType):
|
|
113
|
+
slug = 'switch-triple'
|
|
114
|
+
name = _("Switch Triple")
|
|
115
|
+
description = _("Three-channel on/off actuator.")
|
|
116
|
+
purpose = _("Use to control three separate loads in one device.")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class QuadrupleSwitchType(BaseComponentType):
|
|
120
|
+
slug = 'switch-quadruple'
|
|
121
|
+
name = _("Switch Quadruple")
|
|
122
|
+
description = _("Four-channel on/off actuator.")
|
|
123
|
+
purpose = _("Use to control four loads in one device.")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class QuintupleSwitchType(BaseComponentType):
|
|
127
|
+
slug = 'switch-quintuple'
|
|
128
|
+
name = _("Switch Quintuple")
|
|
129
|
+
description = _("Five-channel on/off actuator.")
|
|
130
|
+
purpose = _("Use to control five loads in one device.")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class DimmerType(BaseComponentType):
|
|
134
|
+
slug = 'dimmer'
|
|
135
|
+
name = _("Dimmer")
|
|
136
|
+
description = _("Continuous actuator with settable output level.")
|
|
137
|
+
purpose = _("Use to control lights or devices with variable output.")
|
|
138
|
+
required_methods = ('turn_on', 'turn_off', 'toggle', 'output_percent', 'max_out')
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DimmerPlusType(BaseComponentType):
|
|
142
|
+
slug = 'dimmer-plus'
|
|
143
|
+
name = _("Dimmer Plus")
|
|
144
|
+
description = _("Multi-channel dimmer with main and secondary outputs.")
|
|
145
|
+
purpose = _("Use for fixtures with multiple dimmable channels.")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class RGBWLightType(BaseComponentType):
|
|
149
|
+
slug = 'rgbw-light'
|
|
150
|
+
name = _("RGB(W) light")
|
|
151
|
+
description = _("Color-capable light with optional white channel.")
|
|
152
|
+
purpose = _("Use for RGB/RGBW lighting control.")
|
|
153
|
+
required_methods = ('turn_on', 'turn_off', 'toggle')
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class LockType(BaseComponentType):
|
|
157
|
+
slug = 'lock'
|
|
158
|
+
name = _("Lock")
|
|
159
|
+
description = _("Door lock actuator with state reporting.")
|
|
160
|
+
purpose = _("Use to control smart locks and display their status.")
|
|
161
|
+
required_methods = ('lock', 'unlock')
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class GateType(BaseComponentType):
|
|
165
|
+
slug = 'gate'
|
|
166
|
+
name = _("Gate")
|
|
167
|
+
description = _("Gate/door opener with open/close/call commands.")
|
|
168
|
+
purpose = _("Use to manage gates with impulse or directional control.")
|
|
169
|
+
required_methods = ('open', 'close', 'call')
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class BlindsType(BaseComponentType):
|
|
173
|
+
slug = 'blinds'
|
|
174
|
+
name = _("Blinds")
|
|
175
|
+
description = _("Window coverings with position and optional angle control.")
|
|
176
|
+
purpose = _("Use to control roller blinds, shades, or venetians.")
|
|
177
|
+
required_methods = ('open', 'close', 'stop')
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _export_base_types_dict():
|
|
181
|
+
"""Derive legacy BASE_TYPES mapping from declared classes.
|
|
182
|
+
|
|
183
|
+
Returns {slug: name} for compatibility with legacy loaders.
|
|
184
|
+
"""
|
|
185
|
+
import inspect as _inspect
|
|
186
|
+
mapping = {}
|
|
187
|
+
g = globals()
|
|
188
|
+
for _name, _obj in g.items():
|
|
189
|
+
if _inspect.isclass(_obj) and issubclass(_obj, BaseComponentType) \
|
|
190
|
+
and _obj is not BaseComponentType and getattr(_obj, 'slug', None):
|
|
191
|
+
mapping[_obj.slug] = _obj.name
|
|
192
|
+
return mapping
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# Backwards-compatible export for code still expecting a dict.
|
|
196
|
+
BASE_TYPES = _export_base_types_dict()
|