matterbridge-litetouch 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,123 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree
8
+ to them as both strict obligations and conditions to all
9
+ your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the software
14
+ to do everything you might do with the software that would
15
+ otherwise infringe the licensor's copyright in it for any
16
+ permitted purpose. However, you may only distribute the software
17
+ according to Distribution License and make changes or new works
18
+ based on the software according to Changes and New Works License.
19
+
20
+ ## Distribution License
21
+
22
+ The licensor grants you an additional copyright license to
23
+ distribute copies of the software. Your license to distribute
24
+ covers distributing the software with changes and new works
25
+ permitted by Changes and New Works License.
26
+
27
+ ## Notices
28
+
29
+ You must ensure that anyone who gets a copy of any part of
30
+ the software from you also gets a copy of these terms or the
31
+ URL for them above, as well as copies of any plain-text lines
32
+ beginning with `Required Notice:` that the licensor provided
33
+ with the software.
34
+
35
+ ## Changes and New Works License
36
+
37
+ The licensor grants you an additional copyright license to
38
+ make changes and new works based on the software for any
39
+ permitted purpose.
40
+
41
+ ## Patent License
42
+
43
+ The licensor grants you a patent license for the software that
44
+ covers patent claims the licensor can license, or becomes able
45
+ to license, that you would infringe by using the software.
46
+
47
+ ## Noncommercial Purposes
48
+
49
+ Any noncommercial purpose is a permitted purpose.
50
+
51
+ ## Personal Uses
52
+
53
+ Personal use for research, experiment, and testing for the
54
+ benefit of public knowledge, personal study, private entertainment,
55
+ hobby projects, amateur pursuits, or religious observance, without
56
+ any anticipated commercial application, is use for a permitted purpose.
57
+
58
+ ## Noncommercial Organizations
59
+
60
+ Use by any charitable organization, educational institution,
61
+ public research organization, public safety or health organization,
62
+ environmental protection organization, or government institution
63
+ is use for a permitted purpose regardless of the source of funding
64
+ or obligations resulting from the funding.
65
+
66
+ ## Fair Use
67
+
68
+ You may have "fair use" rights for the software under the law.
69
+ These terms do not limit them.
70
+
71
+ ## No Other Rights
72
+
73
+ These terms do not allow you to sublicense or transfer any of
74
+ your licenses to anyone else, or prevent the licensor from
75
+ granting licenses to anyone else. These terms do not imply
76
+ any other licenses.
77
+
78
+ ## Patent Defense
79
+
80
+ If you make any written claim that the software infringes or
81
+ contributes to infringement of any patent, your patent license
82
+ for the software granted under these terms ends immediately.
83
+ If your company makes such a claim, your patent license ends
84
+ immediately for work on behalf of your company.
85
+
86
+ ## Violations
87
+
88
+ The first time you are notified in writing that you have
89
+ violated any of these terms, or done anything with the software
90
+ not covered by your licenses, your licenses can nonetheless
91
+ continue if you come into full compliance with these terms,
92
+ and take practical steps to correct past violations, within
93
+ 32 days of receiving notice. Otherwise, all your licenses
94
+ end immediately.
95
+
96
+ ## No Liability
97
+
98
+ ***As far as the law allows, the software comes as is, without
99
+ any warranty or condition, and the licensor will not be liable
100
+ to you for any damages arising out of these terms or the use
101
+ or nature of the software, under any kind of legal claim.***
102
+
103
+ ## Definitions
104
+
105
+ The **licensor** is the individual or entity offering these
106
+ terms, and the **software** is the software the licensor makes
107
+ available under these terms.
108
+
109
+ **You** refers to the individual or entity agreeing to these terms.
110
+
111
+ **Your company** is any legal entity, sole proprietorship,
112
+ or other kind of organization that you work for, plus all
113
+ organizations that have control over, are under the control of,
114
+ or are under common control with that organization. **Control**
115
+ means ownership of substantially all the assets of an entity,
116
+ or the power to direct its management and policies by vote,
117
+ contract, or otherwise. Control can be direct or indirect.
118
+
119
+ **Your licenses** are all the licenses granted to you for
120
+ the software under these terms.
121
+
122
+ **Use** means anything you do with the software requiring
123
+ one of your licenses.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # Matterbridge Litetouch 2000
2
+
3
+ A [Matterbridge](https://github.com/Luligu/matterbridge) plugin that exposes Litetouch 2000 lighting loads as Matter devices.
4
+
5
+ This allows you to control your Litetouch lighting system from any Matter-compatible ecosystem:
6
+ - Apple Home (HomeKit)
7
+ - Google Home
8
+ - Amazon Alexa
9
+ - Home Assistant
10
+ - Hubitat Elevation
11
+ - And more...
12
+
13
+ ## Features
14
+
15
+ - **Dimmers**: Full brightness control with on/off and level adjustment
16
+ - **Switches**: On/off control for relay loads
17
+ - **Status Polling**: Automatically polls loads to detect changes from wall keypads
18
+ - **Priority Queue**: User commands are prioritized over polling for responsive control
19
+ - **Apple Home Optimized**: Includes workaround for Apple Home's command sequencing to prevent brightness flash when turning on dimmers
20
+
21
+ ## Requirements
22
+
23
+ - Raspberry Pi (or similar Linux system) with Node.js 18+
24
+ - USB-to-serial adapter connected to your Litetouch CCU
25
+ - Litetouch 2000 with Standard or Compact CCU (not compatible with 5000LC)
26
+
27
+ ## Hardware Setup
28
+
29
+ ### Serial Cable
30
+
31
+ You need a serial cable between the USB-serial adapter and the Litetouch CCU. Use an RJ45-to-RS232 adapter with the following pinout:
32
+
33
+ | RJ45 Pin | RS232 Signal |
34
+ |----------|--------------|
35
+ | 2 | TX |
36
+ | 3 | RX |
37
+ | 5 | GND |
38
+ | 7 & 8 | Bridge together on CCU side only |
39
+
40
+ Bridging pins 7 & 8 on the CCU side enables polling mode.
41
+
42
+ ### Identify Serial Port
43
+
44
+ After connecting the USB-serial adapter, find the device path:
45
+
46
+ ```bash
47
+ ls -la /dev/ttyUSB*
48
+ # or
49
+ ls -la /dev/serial/by-id/
50
+ ```
51
+
52
+ ## Installation
53
+
54
+ ### 1. Install Matterbridge
55
+
56
+ ```bash
57
+ sudo npm install -g matterbridge
58
+ ```
59
+
60
+ ### 2. Install This Plugin
61
+
62
+ Install globally alongside Matterbridge:
63
+ ```bash
64
+ # Clone the repository
65
+ git clone https://github.com/signal15/matterbridge-litetouch.git
66
+ cd matterbridge-litetouch
67
+
68
+ # Install dependencies and build
69
+ npm install
70
+ npm run build
71
+
72
+ # Install globally (must use sudo since matterbridge runs as root)
73
+ sudo npm install -g .
74
+ ```
75
+
76
+ Then register with Matterbridge:
77
+ ```bash
78
+ sudo matterbridge -add matterbridge-litetouch
79
+ ```
80
+
81
+ ### Updating the Plugin
82
+
83
+ After making changes or pulling updates:
84
+ ```bash
85
+ cd matterbridge-litetouch
86
+ npm run build
87
+ sudo cp -r dist/* /usr/lib/node_modules/matterbridge-litetouch/dist/
88
+ sudo systemctl restart matterbridge
89
+ ```
90
+
91
+ ### 3. Configure the Plugin
92
+
93
+ **Note:** The Matterbridge web UI does not support the array-of-objects format needed for load configuration. Edit the config file directly:
94
+
95
+ ```bash
96
+ sudo nano /root/.matterbridge/matterbridge-litetouch.config.json
97
+ ```
98
+
99
+ After editing, restart matterbridge:
100
+ ```bash
101
+ sudo systemctl restart matterbridge
102
+ ```
103
+
104
+ Configuration options:
105
+
106
+ - **Serial Port**: Path to your USB-serial device (e.g., `/dev/ttyUSB0`)
107
+ - **Baud Rate**: Usually 9600 (default)
108
+ - **Polling Interval**: Time between status polls in milliseconds (default: 2000)
109
+ - **Dimmers**: List of dimmer load addresses with friendly names
110
+ - **Switches**: List of relay load addresses with friendly names
111
+
112
+ ### Example Configuration
113
+
114
+ ```json
115
+ {
116
+ "name": "Litetouch 2000",
117
+ "serialPort": "/dev/ttyUSB0",
118
+ "baudRate": 9600,
119
+ "pollingInterval": 2000,
120
+ "commandTimeout": 1000,
121
+ "dimmers": [
122
+ { "address": "01-1", "name": "Living Room Main" },
123
+ { "address": "01-2", "name": "Living Room Accent" },
124
+ { "address": "02-1", "name": "Kitchen" },
125
+ { "address": "03-4", "name": "Master Bedroom" }
126
+ ],
127
+ "switches": [
128
+ { "address": "05-1", "name": "Garage" },
129
+ { "address": "05-2", "name": "Porch Light" }
130
+ ],
131
+ "debug": false
132
+ }
133
+ ```
134
+
135
+ ## Load Addressing
136
+
137
+ Litetouch loads are addressed as `MM-O` where:
138
+ - `MM` = Module number (1-99)
139
+ - `O` = Output number on that module (1-6 typically)
140
+
141
+ Example addresses: `01-1`, `03-4`, `10-6`
142
+
143
+ To find your load addresses, refer to your Litetouch programming documentation or use the Litetouch Designer software.
144
+
145
+ ## Starting Matterbridge
146
+
147
+ ```bash
148
+ matterbridge
149
+ ```
150
+
151
+ For production use, set up Matterbridge as a systemd service:
152
+
153
+ ```bash
154
+ sudo matterbridge -service install
155
+ sudo systemctl enable matterbridge
156
+ sudo systemctl start matterbridge
157
+ ```
158
+
159
+ ## Commissioning to Your Ecosystem
160
+
161
+ 1. Start Matterbridge and wait for it to initialize
162
+ 2. Open the Matterbridge web UI and note the QR code or pairing code
163
+ 3. In your Matter controller app (Apple Home, Google Home, etc.):
164
+ - Choose "Add Accessory" or "Set up device"
165
+ - Scan the QR code or enter the pairing code
166
+ 4. The Litetouch loads will appear as individual light/switch devices
167
+
168
+ ### Hubitat-Specific Instructions
169
+
170
+ 1. Go to **Devices** → **Add Device** → **Matter**
171
+ 2. Use the Hubitat mobile app to scan the QR code
172
+ 3. Devices will appear automatically in your device list
173
+
174
+ ## Troubleshooting
175
+
176
+ ### Serial Port Access
177
+
178
+ If you get permission errors, add your user to the dialout group:
179
+ ```bash
180
+ sudo usermod -a -G dialout $USER
181
+ # Log out and back in for changes to take effect
182
+ ```
183
+
184
+ ### Debug Logging
185
+
186
+ Enable debug mode in the plugin configuration to see detailed serial communication logs.
187
+
188
+ ### Common Issues
189
+
190
+ | Issue | Solution |
191
+ |-------|----------|
192
+ | "Serial port not found" | Check USB connection and port path |
193
+ | "Permission denied" | Add user to dialout group |
194
+ | "No response from CCU" | Verify cable pinout and baud rate |
195
+ | "Devices not updating" | Check that polling is running in logs |
196
+
197
+ ## Protocol Reference
198
+
199
+ The Litetouch 2000 uses an ASCII protocol over RS-232:
200
+
201
+ - **Query load**: ` 18 MM-O` → Response: `R 18 MM-O LLL`
202
+ - **Set level**: ` 10 MM-O LLL`
203
+ - For relays: `LLL` = `000` (off) or `001` (on)
204
+ - For dimmers: `LLL` = `000`-`250`
205
+
206
+ Commands are terminated with carriage return (`\r`).
207
+
208
+ ## License
209
+
210
+ [PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/) - Free for personal and noncommercial use.
211
+
212
+ ## Credits
213
+
214
+ Based on the original [Vera Litetouch plugin](https://github.com/signal15/vera-litetouch-2000) by signal15.
@@ -0,0 +1,95 @@
1
+ {
2
+ "title": "Litetouch 2000 Configuration",
3
+ "description": "Configure your Litetouch 2000 lighting system connection and loads",
4
+ "type": "object",
5
+ "properties": {
6
+ "name": {
7
+ "title": "Plugin Name",
8
+ "description": "Name displayed in Matterbridge",
9
+ "type": "string",
10
+ "default": "Litetouch 2000"
11
+ },
12
+ "serialPort": {
13
+ "title": "Serial Port",
14
+ "description": "Path to the USB-serial device (e.g., /dev/ttyUSB0)",
15
+ "type": "string",
16
+ "default": "/dev/ttyUSB0"
17
+ },
18
+ "baudRate": {
19
+ "title": "Baud Rate",
20
+ "description": "Serial port baud rate",
21
+ "type": "integer",
22
+ "default": 9600,
23
+ "enum": [9600, 19200, 38400, 57600, 115200]
24
+ },
25
+ "pollingInterval": {
26
+ "title": "Polling Interval (ms)",
27
+ "description": "Time between polling each load for status updates (minimum 500ms)",
28
+ "type": "integer",
29
+ "default": 2000,
30
+ "minimum": 500,
31
+ "maximum": 10000
32
+ },
33
+ "commandTimeout": {
34
+ "title": "Command Timeout (ms)",
35
+ "description": "How long to wait for a response before timing out",
36
+ "type": "integer",
37
+ "default": 1000,
38
+ "minimum": 250,
39
+ "maximum": 5000
40
+ },
41
+ "dimmers": {
42
+ "title": "Dimmer Loads",
43
+ "description": "List of dimmer module-output addresses (e.g., 01-1, 03-4)",
44
+ "type": "array",
45
+ "items": {
46
+ "type": "object",
47
+ "properties": {
48
+ "address": {
49
+ "title": "Address",
50
+ "description": "Module-output address (e.g., 01-1)",
51
+ "type": "string",
52
+ "pattern": "^\\d{1,2}-\\d{1,2}$"
53
+ },
54
+ "name": {
55
+ "title": "Name",
56
+ "description": "Friendly name for this load",
57
+ "type": "string"
58
+ }
59
+ },
60
+ "required": ["address", "name"]
61
+ },
62
+ "default": []
63
+ },
64
+ "switches": {
65
+ "title": "Relay/Switch Loads",
66
+ "description": "List of relay/switch module-output addresses (e.g., 02-3, 05-6)",
67
+ "type": "array",
68
+ "items": {
69
+ "type": "object",
70
+ "properties": {
71
+ "address": {
72
+ "title": "Address",
73
+ "description": "Module-output address (e.g., 02-3)",
74
+ "type": "string",
75
+ "pattern": "^\\d{1,2}-\\d{1,2}$"
76
+ },
77
+ "name": {
78
+ "title": "Name",
79
+ "description": "Friendly name for this load",
80
+ "type": "string"
81
+ }
82
+ },
83
+ "required": ["address", "name"]
84
+ },
85
+ "default": []
86
+ },
87
+ "debug": {
88
+ "title": "Debug Logging",
89
+ "description": "Enable verbose debug logging",
90
+ "type": "boolean",
91
+ "default": false
92
+ }
93
+ },
94
+ "required": ["serialPort", "dimmers", "switches"]
95
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Command queue with priority handling for Litetouch serial communication.
3
+ * User-initiated commands (on/off, dimming) take priority over background polling.
4
+ */
5
+ export declare enum CommandPriority {
6
+ HIGH = 0,// User-initiated commands (on/off, set level)
7
+ NORMAL = 1
8
+ }
9
+ export interface QueuedCommand {
10
+ command: string;
11
+ priority: CommandPriority;
12
+ resolve: (response: string | null) => void;
13
+ reject: (error: Error) => void;
14
+ timestamp: number;
15
+ }
16
+ export declare class CommandQueue {
17
+ private queue;
18
+ private processing;
19
+ private processCallback;
20
+ /**
21
+ * Set the callback that processes commands (sends to serial port)
22
+ */
23
+ setProcessor(callback: (cmd: QueuedCommand) => Promise<string | null>): void;
24
+ /**
25
+ * Add a command to the queue with specified priority.
26
+ * High priority commands are inserted before normal priority ones.
27
+ */
28
+ enqueue(command: string, priority?: CommandPriority): Promise<string | null>;
29
+ /**
30
+ * Add a high-priority command (user action)
31
+ */
32
+ enqueueHighPriority(command: string): Promise<string | null>;
33
+ /**
34
+ * Add a normal-priority command (polling)
35
+ */
36
+ enqueuePolling(command: string): Promise<string | null>;
37
+ /**
38
+ * Process the next command in the queue
39
+ */
40
+ private processNext;
41
+ /**
42
+ * Get the current queue length
43
+ */
44
+ get length(): number;
45
+ /**
46
+ * Check if currently processing a command
47
+ */
48
+ get isProcessing(): boolean;
49
+ /**
50
+ * Clear all pending commands (used during shutdown)
51
+ */
52
+ clear(): void;
53
+ /**
54
+ * Remove all polling commands from the queue (useful when user commands come in)
55
+ */
56
+ clearPollingCommands(): void;
57
+ }
58
+ //# sourceMappingURL=commandQueue.d.ts.map
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Command queue with priority handling for Litetouch serial communication.
3
+ * User-initiated commands (on/off, dimming) take priority over background polling.
4
+ */
5
+ export var CommandPriority;
6
+ (function (CommandPriority) {
7
+ CommandPriority[CommandPriority["HIGH"] = 0] = "HIGH";
8
+ CommandPriority[CommandPriority["NORMAL"] = 1] = "NORMAL";
9
+ })(CommandPriority || (CommandPriority = {}));
10
+ export class CommandQueue {
11
+ queue = [];
12
+ processing = false;
13
+ processCallback = null;
14
+ /**
15
+ * Set the callback that processes commands (sends to serial port)
16
+ */
17
+ setProcessor(callback) {
18
+ this.processCallback = callback;
19
+ }
20
+ /**
21
+ * Add a command to the queue with specified priority.
22
+ * High priority commands are inserted before normal priority ones.
23
+ */
24
+ async enqueue(command, priority = CommandPriority.NORMAL) {
25
+ return new Promise((resolve, reject) => {
26
+ const queuedCmd = {
27
+ command,
28
+ priority,
29
+ resolve,
30
+ reject,
31
+ timestamp: Date.now(),
32
+ };
33
+ // Insert based on priority
34
+ if (priority === CommandPriority.HIGH) {
35
+ // Find the first normal priority command and insert before it
36
+ const insertIndex = this.queue.findIndex(cmd => cmd.priority === CommandPriority.NORMAL);
37
+ if (insertIndex === -1) {
38
+ this.queue.push(queuedCmd);
39
+ }
40
+ else {
41
+ this.queue.splice(insertIndex, 0, queuedCmd);
42
+ }
43
+ }
44
+ else {
45
+ this.queue.push(queuedCmd);
46
+ }
47
+ // Start processing if not already running
48
+ this.processNext();
49
+ });
50
+ }
51
+ /**
52
+ * Add a high-priority command (user action)
53
+ */
54
+ async enqueueHighPriority(command) {
55
+ return this.enqueue(command, CommandPriority.HIGH);
56
+ }
57
+ /**
58
+ * Add a normal-priority command (polling)
59
+ */
60
+ async enqueuePolling(command) {
61
+ return this.enqueue(command, CommandPriority.NORMAL);
62
+ }
63
+ /**
64
+ * Process the next command in the queue
65
+ */
66
+ async processNext() {
67
+ if (this.processing || this.queue.length === 0 || !this.processCallback) {
68
+ return;
69
+ }
70
+ this.processing = true;
71
+ const cmd = this.queue.shift();
72
+ try {
73
+ const response = await this.processCallback(cmd);
74
+ cmd.resolve(response);
75
+ }
76
+ catch (error) {
77
+ cmd.reject(error instanceof Error ? error : new Error(String(error)));
78
+ }
79
+ finally {
80
+ this.processing = false;
81
+ // Process next command if queue is not empty
82
+ if (this.queue.length > 0) {
83
+ // Use setImmediate to prevent stack overflow on large queues
84
+ setImmediate(() => this.processNext());
85
+ }
86
+ }
87
+ }
88
+ /**
89
+ * Get the current queue length
90
+ */
91
+ get length() {
92
+ return this.queue.length;
93
+ }
94
+ /**
95
+ * Check if currently processing a command
96
+ */
97
+ get isProcessing() {
98
+ return this.processing;
99
+ }
100
+ /**
101
+ * Clear all pending commands (used during shutdown)
102
+ */
103
+ clear() {
104
+ for (const cmd of this.queue) {
105
+ cmd.reject(new Error('Queue cleared'));
106
+ }
107
+ this.queue = [];
108
+ }
109
+ /**
110
+ * Remove all polling commands from the queue (useful when user commands come in)
111
+ */
112
+ clearPollingCommands() {
113
+ this.queue = this.queue.filter(cmd => {
114
+ if (cmd.priority === CommandPriority.NORMAL) {
115
+ cmd.reject(new Error('Polling command cancelled'));
116
+ return false;
117
+ }
118
+ return true;
119
+ });
120
+ }
121
+ }
122
+ //# sourceMappingURL=commandQueue.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Matterbridge Plugin for Litetouch 2000 Lighting System
3
+ *
4
+ * Exposes Litetouch dimmers and switches as Matter devices that can be
5
+ * controlled from any Matter-compatible ecosystem (Apple Home, Google Home,
6
+ * Amazon Alexa, Home Assistant, Hubitat, etc.)
7
+ */
8
+ import { MatterbridgeDynamicPlatform, PlatformConfig, PlatformMatterbridge } from 'matterbridge';
9
+ import { AnsiLogger } from 'matterbridge/logger';
10
+ /**
11
+ * Factory function to create the Litetouch platform
12
+ * This is the entry point for Matterbridge
13
+ */
14
+ export default function createPlatform(matterbridge: PlatformMatterbridge, log: AnsiLogger, config: PlatformConfig): MatterbridgeDynamicPlatform;
15
+ export { LitetouchPlatform } from './platform.js';
16
+ export { LitetouchConnection } from './litetouchConnection.js';
17
+ export { CommandQueue, CommandPriority } from './commandQueue.js';
18
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Matterbridge Plugin for Litetouch 2000 Lighting System
3
+ *
4
+ * Exposes Litetouch dimmers and switches as Matter devices that can be
5
+ * controlled from any Matter-compatible ecosystem (Apple Home, Google Home,
6
+ * Amazon Alexa, Home Assistant, Hubitat, etc.)
7
+ */
8
+ import { LitetouchPlatform } from './platform.js';
9
+ /**
10
+ * Factory function to create the Litetouch platform
11
+ * This is the entry point for Matterbridge
12
+ */
13
+ export default function createPlatform(matterbridge, log, config) {
14
+ return new LitetouchPlatform(matterbridge, log, config);
15
+ }
16
+ // Re-export the platform class for advanced usage
17
+ export { LitetouchPlatform } from './platform.js';
18
+ export { LitetouchConnection } from './litetouchConnection.js';
19
+ export { CommandQueue, CommandPriority } from './commandQueue.js';
20
+ //# sourceMappingURL=index.js.map