homebridge-smartika 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 +21 -0
- package/README.md +367 -0
- package/config.schema.json +71 -0
- package/package.json +57 -0
- package/src/SmartikaCrypto.js +134 -0
- package/src/SmartikaDiscovery.js +177 -0
- package/src/SmartikaHubConnection.js +528 -0
- package/src/SmartikaPlatform.js +379 -0
- package/src/SmartikaProtocol.js +977 -0
- package/src/accessories/SmartikaFanAccessory.js +162 -0
- package/src/accessories/SmartikaLightAccessory.js +203 -0
- package/src/accessories/SmartikaPlugAccessory.js +112 -0
- package/src/index.js +12 -0
- package/src/settings.js +16 -0
- package/tools/smartika-cli.js +1443 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Philippe Blondin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# Homebridge Smartika
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/homebridge-smartika)
|
|
4
|
+
[](https://www.npmjs.com/package/homebridge-smartika)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A [Homebridge](https://homebridge.io) plugin for **Smartika** (Artika) smart home devices with **100% local control** — no cloud required!
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🏠 **100% Local Control** — All communication stays on your local network
|
|
12
|
+
- � **Auto-Discovery** — Automatically finds your Smartika hub on the network
|
|
13
|
+
- �💡 **Lights** — On/off, brightness, and color temperature control
|
|
14
|
+
- 🌀 **Ceiling Fans** — On/off and speed control
|
|
15
|
+
- 🔌 **Smart Plugs** — On/off control
|
|
16
|
+
- 🔄 **Real-time Updates** — Device status polling keeps HomeKit in sync
|
|
17
|
+
- 🔐 **Secure** — AES-128-CBC encrypted communication with your hub
|
|
18
|
+
- 🛠️ **CLI Tool** — Command-line interface for debugging and direct control
|
|
19
|
+
|
|
20
|
+
## Supported Devices
|
|
21
|
+
|
|
22
|
+
| Category | Devices |
|
|
23
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| **Lights** | Champagne Track, Mini Wall Washer, Glowbox, Recessed Lighting, Pendants (1-5), Smart Bulb, Spotlight, Sandwich Lights |
|
|
25
|
+
| **Fans** | Ceiling Fan |
|
|
26
|
+
| **Plugs** | Smart Plug |
|
|
27
|
+
| **Thermostats** | Thermostat, Smart Heater *(coming soon)* |
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- [Homebridge](https://homebridge.io) v1.8.0 or later
|
|
32
|
+
- Node.js v18.20.4, v20.16.0, or v22.5.1+
|
|
33
|
+
- Smartika Hub on your local network
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
### Using Homebridge Config UI X (Recommended)
|
|
38
|
+
|
|
39
|
+
1. Search for **"Smartika"** in the Plugins tab
|
|
40
|
+
2. Click **Install**
|
|
41
|
+
3. Configure the plugin with your hub's IP address
|
|
42
|
+
|
|
43
|
+
### Manual Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
sudo npm install -g homebridge-smartika
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
### Auto-Discovery (Recommended)
|
|
52
|
+
|
|
53
|
+
The plugin can automatically discover your Smartika hub on the network — no manual configuration needed! Simply install the plugin and restart Homebridge.
|
|
54
|
+
|
|
55
|
+
The hub broadcasts its presence on UDP port 4156 every ~10 seconds. If auto-discovery doesn't work (e.g., due to network segmentation or firewall rules), you can configure the hub IP manually.
|
|
56
|
+
|
|
57
|
+
### Finding Your Hub IP Address (Manual)
|
|
58
|
+
|
|
59
|
+
If auto-discovery doesn't work, find your hub's IP address by:
|
|
60
|
+
|
|
61
|
+
1. Checking your router's DHCP client list
|
|
62
|
+
2. Using a network scanner app
|
|
63
|
+
3. Looking for a device with MAC address starting with `00:12:4B`
|
|
64
|
+
|
|
65
|
+
### Using Homebridge Config UI X
|
|
66
|
+
|
|
67
|
+
1. Go to the **Plugins** tab
|
|
68
|
+
2. Find **Homebridge Smartika** and click **Settings**
|
|
69
|
+
3. Enter your hub's IP address
|
|
70
|
+
4. Click **Save**
|
|
71
|
+
|
|
72
|
+
### Manual Configuration
|
|
73
|
+
|
|
74
|
+
Add the following to your `config.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"platforms": [
|
|
79
|
+
{
|
|
80
|
+
"platform": "Smartika",
|
|
81
|
+
"name": "Smartika Hub"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
With manual hub IP (if auto-discovery doesn't work):
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"platforms": [
|
|
92
|
+
{
|
|
93
|
+
"platform": "Smartika",
|
|
94
|
+
"name": "Smartika Hub",
|
|
95
|
+
"hubHost": "10.0.0.122"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
With all options:
|
|
102
|
+
|
|
103
|
+
### Configuration Options
|
|
104
|
+
|
|
105
|
+
| Option | Required | Default | Description |
|
|
106
|
+
| ----------------- | -------- | ---------------- | ------------------------------------------------------------ |
|
|
107
|
+
| `platform` | ✅ | — | Must be `"Smartika"` |
|
|
108
|
+
| `name` | ❌ | `"Smartika Hub"` | Display name in Homebridge logs |
|
|
109
|
+
| `hubHost` | ❌ | Auto-discover | IP address of your Smartika hub (auto-discovered if not set) |
|
|
110
|
+
| `hubPort` | ❌ | `1234` | TCP port for hub communication |
|
|
111
|
+
| `pollingInterval` | ❌ | `5000` | Status polling interval in milliseconds |
|
|
112
|
+
| `debug` | ❌ | `false` | Enable verbose debug logging |
|
|
113
|
+
|
|
114
|
+
## CLI Tool
|
|
115
|
+
|
|
116
|
+
This plugin includes a powerful command-line interface for direct hub control and debugging.
|
|
117
|
+
|
|
118
|
+
### Installation
|
|
119
|
+
|
|
120
|
+
The CLI is installed automatically with the plugin:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# If installed globally
|
|
124
|
+
smartika-cli --help
|
|
125
|
+
|
|
126
|
+
# Or run directly
|
|
127
|
+
npx smartika-cli --help
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Usage
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Discover hubs on the network (no IP needed)
|
|
134
|
+
smartika-cli hub-discover
|
|
135
|
+
|
|
136
|
+
# Run commands on a specific hub
|
|
137
|
+
smartika-cli <hub-ip> <command> [arguments...]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Examples
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Discover Smartika hubs on your network
|
|
144
|
+
smartika-cli hub-discover
|
|
145
|
+
|
|
146
|
+
# Get hub information (ID, MAC, firmware, encryption key)
|
|
147
|
+
smartika-cli 10.0.0.122 hub-info
|
|
148
|
+
|
|
149
|
+
# Get status of all devices
|
|
150
|
+
smartika-cli 10.0.0.122 status
|
|
151
|
+
|
|
152
|
+
# Turn on a device
|
|
153
|
+
smartika-cli 10.0.0.122 on 0x28cf
|
|
154
|
+
|
|
155
|
+
# Set brightness to 50%
|
|
156
|
+
smartika-cli 10.0.0.122 dim 50% 0x28cf
|
|
157
|
+
|
|
158
|
+
# Set color temperature (0=warm, 255=cool)
|
|
159
|
+
smartika-cli 10.0.0.122 temp 128 0x28cf
|
|
160
|
+
|
|
161
|
+
# List registered devices
|
|
162
|
+
smartika-cli 10.0.0.122 list
|
|
163
|
+
|
|
164
|
+
# Get firmware version
|
|
165
|
+
smartika-cli 10.0.0.122 firmware
|
|
166
|
+
|
|
167
|
+
# Preview what devices will appear in HomeKit
|
|
168
|
+
smartika-cli 10.0.0.122 homekit-preview
|
|
169
|
+
|
|
170
|
+
# Interactive pairing wizard for new devices
|
|
171
|
+
smartika-cli 10.0.0.122 pair
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Available Commands
|
|
175
|
+
|
|
176
|
+
| Category | Command | Description |
|
|
177
|
+
| ----------------- | -------------- | ----------------------------------------- |
|
|
178
|
+
| **Hub Discovery** | `hub-discover` | Find hubs on the network (no IP needed) |
|
|
179
|
+
| **System** | `hub-info` | Get hub ID, MAC, firmware, encryption key |
|
|
180
|
+
| | `ping` | Send keep-alive ping |
|
|
181
|
+
| | `firmware` | Get hub firmware version |
|
|
182
|
+
| | `join-enable` | Enable device pairing mode |
|
|
183
|
+
| | `join-disable` | Disable device pairing mode |
|
|
184
|
+
| **Device** | `discover` | Discover active devices |
|
|
185
|
+
| | `status` | Get device status |
|
|
186
|
+
| | `on` | Turn device(s) on |
|
|
187
|
+
| | `off` | Turn device(s) off |
|
|
188
|
+
| | `dim` | Set light brightness |
|
|
189
|
+
| | `temp` | Set color temperature |
|
|
190
|
+
| | `fan` | Set fan speed |
|
|
191
|
+
| **Database** | `list` | List registered devices |
|
|
192
|
+
| | `db-add` | Add device(s) to database |
|
|
193
|
+
| | `db-remove` | Remove device(s) from database |
|
|
194
|
+
| **Groups** | `groups` | List all groups |
|
|
195
|
+
| | `group-read` | Read group members |
|
|
196
|
+
| | `group-create` | Create a new group |
|
|
197
|
+
| | `group-update` | Update group members |
|
|
198
|
+
| | `group-delete` | Delete group(s) |
|
|
199
|
+
| **HomeKit** | `homekit-preview` | Preview HomeKit accessories |
|
|
200
|
+
| **Pairing** | `pair` | Interactive wizard to pair new devices |
|
|
201
|
+
|
|
202
|
+
## Troubleshooting
|
|
203
|
+
|
|
204
|
+
### Auto-Discovery Not Working
|
|
205
|
+
|
|
206
|
+
1. **macOS firewall** — On macOS, you may need to allow Node.js to receive incoming connections:
|
|
207
|
+
- Open **System Settings** → **Network** → **Firewall** → **Options**
|
|
208
|
+
- Add Node.js (run `which node` to find the path) and set to **Allow incoming connections**
|
|
209
|
+
- Or run the CLI with `sudo` to bypass the firewall temporarily
|
|
210
|
+
2. **Check firewall** — Ensure UDP port 4156 is not blocked
|
|
211
|
+
3. **Same network** — Hub and Homebridge must be on the same subnet
|
|
212
|
+
4. **Use CLI to test** — Verify discovery works:
|
|
213
|
+
```bash
|
|
214
|
+
# May need sudo on macOS if firewall blocks UDP
|
|
215
|
+
sudo smartika-cli hub-discover
|
|
216
|
+
```
|
|
217
|
+
5. **Fallback to manual** — Configure `hubHost` manually if discovery fails
|
|
218
|
+
|
|
219
|
+
### Hub Not Connecting
|
|
220
|
+
|
|
221
|
+
1. **Verify the IP address** — Make sure your hub's IP hasn't changed (consider setting a DHCP reservation)
|
|
222
|
+
2. **Check network connectivity** — Ensure Homebridge can reach the hub: `ping 10.0.0.122`
|
|
223
|
+
3. **Test with CLI** — Use the CLI tool to verify connectivity:
|
|
224
|
+
```bash
|
|
225
|
+
smartika-cli 10.0.0.122 ping
|
|
226
|
+
```
|
|
227
|
+
4. **Check firewall** — Ensure port 1234 (TCP) is not blocked
|
|
228
|
+
|
|
229
|
+
### Devices Not Appearing
|
|
230
|
+
|
|
231
|
+
1. **Check device registration** — Devices must be registered in the hub's database:
|
|
232
|
+
```bash
|
|
233
|
+
smartika-cli 10.0.0.122 list
|
|
234
|
+
```
|
|
235
|
+
2. **Pair new devices** — Use the interactive pairing wizard:
|
|
236
|
+
```bash
|
|
237
|
+
smartika-cli 10.0.0.122 pair
|
|
238
|
+
```
|
|
239
|
+
Or manually discover and add:
|
|
240
|
+
```bash
|
|
241
|
+
smartika-cli 10.0.0.122 discover
|
|
242
|
+
smartika-cli 10.0.0.122 db-add 0x28cf
|
|
243
|
+
```
|
|
244
|
+
3. **Preview HomeKit accessories** — Check what will appear in HomeKit:
|
|
245
|
+
```bash
|
|
246
|
+
smartika-cli 10.0.0.122 homekit-preview
|
|
247
|
+
```
|
|
248
|
+
4. **Restart Homebridge** — After adding devices, restart Homebridge to re-discover
|
|
249
|
+
|
|
250
|
+
### Status Not Updating
|
|
251
|
+
|
|
252
|
+
1. **Increase polling frequency** — Reduce `pollingInterval` to 2000-3000ms
|
|
253
|
+
2. **Enable debug logging** — Set `"debug": true` to see communication details
|
|
254
|
+
3. **Check for errors** — Look for error messages in Homebridge logs
|
|
255
|
+
|
|
256
|
+
### Debug Mode
|
|
257
|
+
|
|
258
|
+
Enable debug logging to see detailed communication:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"platform": "Smartika",
|
|
263
|
+
"hubHost": "10.0.0.122",
|
|
264
|
+
"debug": true
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This will log:
|
|
269
|
+
- Connection status
|
|
270
|
+
- Request/response packets (hex)
|
|
271
|
+
- Device status updates
|
|
272
|
+
- Error details
|
|
273
|
+
|
|
274
|
+
## Technical Details
|
|
275
|
+
|
|
276
|
+
### Protocol
|
|
277
|
+
|
|
278
|
+
The plugin communicates with the Smartika hub using:
|
|
279
|
+
- **Transport**: TCP on port 1234
|
|
280
|
+
- **Encryption**: AES-128-CBC with key derived from hub MAC address
|
|
281
|
+
- **Protocol**: Custom binary protocol with XOR checksum
|
|
282
|
+
|
|
283
|
+
### Security
|
|
284
|
+
|
|
285
|
+
- The encryption key is derived from your hub's unique MAC address using 8-pass AES-ECB
|
|
286
|
+
- All commands are encrypted before transmission
|
|
287
|
+
- No data is sent to external servers
|
|
288
|
+
|
|
289
|
+
### Architecture
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
|
|
293
|
+
│ Apple Home │────▶│ Homebridge │────▶│ Smartika │
|
|
294
|
+
│ (iOS) │◀────│ Plugin │◀────│ Hub │
|
|
295
|
+
└─────────────────┘ └──────────────────┘ └─────────────┘
|
|
296
|
+
HomeKit TCP/AES-CBC Local Network
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Development
|
|
300
|
+
|
|
301
|
+
### Project Structure
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
smartika-homebridge/
|
|
305
|
+
├── src/
|
|
306
|
+
│ ├── index.js # Plugin entry point
|
|
307
|
+
│ ├── settings.js # Plugin constants
|
|
308
|
+
│ ├── SmartikaPlatform.js # Main platform class
|
|
309
|
+
│ ├── SmartikaHubConnection.js # Hub communication
|
|
310
|
+
│ ├── SmartikaCrypto.js # AES encryption
|
|
311
|
+
│ ├── SmartikaProtocol.js # Binary protocol
|
|
312
|
+
│ └── accessories/
|
|
313
|
+
│ ├── SmartikaLightAccessory.js
|
|
314
|
+
│ ├── SmartikaFanAccessory.js
|
|
315
|
+
│ └── SmartikaPlugAccessory.js
|
|
316
|
+
├── tools/
|
|
317
|
+
│ └── smartika-cli.js # CLI tool
|
|
318
|
+
├── test/
|
|
319
|
+
│ ├── test-protocol.js
|
|
320
|
+
│ └── test-crypto.js
|
|
321
|
+
├── config.schema.json # Homebridge UI schema
|
|
322
|
+
└── package.json
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Building & Testing
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# Clone the repository
|
|
329
|
+
git clone https://github.com/pblondin/smartika-homebridge.git
|
|
330
|
+
cd smartika-homebridge
|
|
331
|
+
|
|
332
|
+
# Install dependencies
|
|
333
|
+
npm install
|
|
334
|
+
|
|
335
|
+
# Run tests
|
|
336
|
+
npm test
|
|
337
|
+
npm run test:crypto
|
|
338
|
+
|
|
339
|
+
# Lint code
|
|
340
|
+
npm run lint
|
|
341
|
+
|
|
342
|
+
# Link for local development
|
|
343
|
+
npm link
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Contributing
|
|
347
|
+
|
|
348
|
+
Contributions are welcome! Please:
|
|
349
|
+
|
|
350
|
+
1. Fork the repository
|
|
351
|
+
2. Create a feature branch
|
|
352
|
+
3. Make your changes
|
|
353
|
+
4. Run tests and linting
|
|
354
|
+
5. Submit a pull request
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
359
|
+
|
|
360
|
+
## Acknowledgments
|
|
361
|
+
|
|
362
|
+
- [Homebridge](https://homebridge.io) team for the amazing platform
|
|
363
|
+
- Smartika/Artika for the hardware
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
**Note**: This plugin is not officially affiliated with or endorsed by Smartika or Artika. Use at your own risk.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "Smartika",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"singular": true,
|
|
5
|
+
"headerDisplay": "Homebridge plugin for Smartika smart lighting hub with 100% local communication (no cloud required). Supports lights, fans, and smart plugs. Leave Hub IP empty for auto-discovery.",
|
|
6
|
+
"footerDisplay": "For help and documentation, see the [GitHub repository](https://github.com/pblondin/smartika-homebridge). Use `smartika-cli hub-discover` to find your hub.",
|
|
7
|
+
"schema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"name": {
|
|
11
|
+
"title": "Platform Name",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "Smartika Hub",
|
|
14
|
+
"required": true,
|
|
15
|
+
"description": "The name that will appear in Homebridge logs."
|
|
16
|
+
},
|
|
17
|
+
"hubHost": {
|
|
18
|
+
"title": "Hub IP Address",
|
|
19
|
+
"type": "string",
|
|
20
|
+
"format": "ipv4",
|
|
21
|
+
"placeholder": "10.0.0.122",
|
|
22
|
+
"description": "IP address of your Smartika hub. Leave empty for auto-discovery (requires UDP port 4156). Recommended to set a static IP or DHCP reservation."
|
|
23
|
+
},
|
|
24
|
+
"hubPort": {
|
|
25
|
+
"title": "Hub Port",
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"default": 1234,
|
|
28
|
+
"minimum": 1,
|
|
29
|
+
"maximum": 65535,
|
|
30
|
+
"description": "TCP port for hub communication (default: 1234)."
|
|
31
|
+
},
|
|
32
|
+
"pollingInterval": {
|
|
33
|
+
"title": "Polling Interval (ms)",
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"default": 5000,
|
|
36
|
+
"minimum": 1000,
|
|
37
|
+
"maximum": 60000,
|
|
38
|
+
"description": "How often to poll the hub for device status updates (in milliseconds)."
|
|
39
|
+
},
|
|
40
|
+
"debug": {
|
|
41
|
+
"title": "Debug Mode",
|
|
42
|
+
"type": "boolean",
|
|
43
|
+
"default": false,
|
|
44
|
+
"description": "Enable verbose debug logging for troubleshooting connection issues."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": []
|
|
48
|
+
},
|
|
49
|
+
"layout": [
|
|
50
|
+
{
|
|
51
|
+
"type": "fieldset",
|
|
52
|
+
"title": "Hub Connection",
|
|
53
|
+
"expandable": false,
|
|
54
|
+
"items": [
|
|
55
|
+
"name",
|
|
56
|
+
"hubHost",
|
|
57
|
+
"hubPort"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "fieldset",
|
|
62
|
+
"title": "Advanced Settings",
|
|
63
|
+
"expandable": true,
|
|
64
|
+
"expanded": false,
|
|
65
|
+
"items": [
|
|
66
|
+
"pollingInterval",
|
|
67
|
+
"debug"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "homebridge-smartika",
|
|
3
|
+
"displayName": "Homebridge Smartika",
|
|
4
|
+
"description": "Homebridge plugin for Smartika smart lighting hub with 100% local control (no cloud required). Supports lights, fans, and smart plugs.",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Philippe Blondin",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pblondin/homebridge-smartika.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pblondin/homebridge-smartika/issues"
|
|
14
|
+
},
|
|
15
|
+
"funding": {
|
|
16
|
+
"type": "github",
|
|
17
|
+
"url": "https://github.com/sponsors/pblondin"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"homebridge-plugin",
|
|
21
|
+
"smartika",
|
|
22
|
+
"artika",
|
|
23
|
+
"homekit",
|
|
24
|
+
"smart-home",
|
|
25
|
+
"lighting",
|
|
26
|
+
"local-control"
|
|
27
|
+
],
|
|
28
|
+
"main": "src/index.js",
|
|
29
|
+
"files": [
|
|
30
|
+
"src/",
|
|
31
|
+
"tools/",
|
|
32
|
+
"config.schema.json",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": "^18.20.4 || ^20.16.0 || ^22.5.1",
|
|
38
|
+
"homebridge": "^1.8.0 || ^2.0.0-beta.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"cli": "node tools/smartika-cli.js",
|
|
42
|
+
"test": "node test/test-protocol.js",
|
|
43
|
+
"test:crypto": "node test/test-crypto.js",
|
|
44
|
+
"lint": "eslint src/",
|
|
45
|
+
"watch": "nodemon",
|
|
46
|
+
"prepublishOnly": "npm run lint"
|
|
47
|
+
},
|
|
48
|
+
"bin": {
|
|
49
|
+
"smartika-cli": "tools/smartika-cli.js"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/js": "^9.0.0",
|
|
54
|
+
"eslint": "^9.0.0",
|
|
55
|
+
"nodemon": "^3.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// Constants from hub_security.md
|
|
6
|
+
const PRIVATE_KEY = Buffer.from([
|
|
7
|
+
0x42, 0x6B, 0xD6, 0xCD, 0x00, 0x59, 0x5B, 0x03, 0xFC, 0xCB, 0xF4, 0xDD, 0x09, 0x25, 0x85, 0x1B,
|
|
8
|
+
0x3F, 0x91, 0x93, 0x81, 0xB3, 0x19, 0xB2, 0xD1, 0x41, 0x5B, 0xF7, 0x7D, 0xFD, 0x4F, 0x4C, 0xD3,
|
|
9
|
+
0x5E, 0x00, 0xE1, 0xC0, 0x89, 0xA1, 0x94, 0xD4, 0xF6, 0xEA, 0x77, 0xAA, 0xC5, 0x1B, 0x66, 0x67,
|
|
10
|
+
0xEE, 0x96, 0xCD, 0x6E, 0xC3, 0x7D, 0x8A, 0xF1, 0xD0, 0x2A, 0x10, 0x98, 0xA7, 0xF5, 0xB1, 0xC3,
|
|
11
|
+
0x90, 0x3A, 0x4A, 0xB7, 0xB9, 0xE5, 0x0E, 0x47, 0xE5, 0xA0, 0xD2, 0x1B, 0x17, 0xD0, 0x8B, 0x5A,
|
|
12
|
+
0x55, 0x7C, 0x50, 0xBA, 0x02, 0x66, 0xA7, 0xC1, 0xCC, 0x4D, 0x67, 0x3E, 0xD1, 0xB7, 0xEE, 0xC0,
|
|
13
|
+
0xE3, 0x34, 0x00, 0x1F, 0x89, 0x7A, 0x0E, 0xC7, 0xC0, 0x49, 0x2F, 0xEE, 0x01, 0x7B, 0x94, 0x52,
|
|
14
|
+
0x93, 0x22, 0xC0, 0xB9, 0xBB, 0x2C, 0x46, 0xD1, 0xBD, 0x65, 0x5F, 0x91, 0x56, 0x4B, 0x17, 0xCD
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const BASE_KEY = Buffer.from([
|
|
18
|
+
0xB9, 0x43, 0x34, 0xB5, 0xBE, 0xDE, 0x9E, 0x05, 0x58, 0xE2, 0xE6, 0xD8, 0xCE, 0xBA, 0x7E, 0x47
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const IV = Buffer.from([
|
|
22
|
+
0xA7, 0x2D, 0xD1, 0x29, 0x20, 0xDF, 0xAD, 0x61, 0x82, 0x03, 0x98, 0xFA, 0x9E, 0xEF, 0x59, 0x20
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const BLOCK_SIZE = 16;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate encryption key from hub MAC address
|
|
29
|
+
* Uses specific bytes from the MAC address as per hub simulator's security.py
|
|
30
|
+
*
|
|
31
|
+
* @param {Buffer} macAddress - 6 bytes MAC address
|
|
32
|
+
* @returns {Buffer} - 16 bytes encryption key
|
|
33
|
+
*/
|
|
34
|
+
function generateKey(macAddress) {
|
|
35
|
+
if (macAddress.length !== 6) {
|
|
36
|
+
throw new Error('MAC address must be 6 bytes');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Insert MAC address bytes in specific positions (from security.py)
|
|
40
|
+
// seed = hub_id[-6:] (entire MAC for 6-byte MAC)
|
|
41
|
+
// base_key[9] = seed[0], base_key[7] = seed[3], base_key[13] = seed[4], base_key[3] = seed[5]
|
|
42
|
+
const baseKey = Buffer.from(BASE_KEY);
|
|
43
|
+
baseKey[9] = macAddress[0];
|
|
44
|
+
baseKey[7] = macAddress[3];
|
|
45
|
+
baseKey[13] = macAddress[4];
|
|
46
|
+
baseKey[3] = macAddress[5];
|
|
47
|
+
|
|
48
|
+
// 8-pass encryption (1024-bit)
|
|
49
|
+
let result = baseKey;
|
|
50
|
+
for (let i = 0; i < 8; i++) {
|
|
51
|
+
const key = PRIVATE_KEY.subarray(i * 16, (i + 1) * 16);
|
|
52
|
+
const cipher = crypto.createCipheriv('aes-128-ecb', key, null);
|
|
53
|
+
cipher.setAutoPadding(false);
|
|
54
|
+
result = Buffer.concat([cipher.update(result), cipher.final()]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Pad message to multiple of 16 bytes with random data
|
|
62
|
+
* @param {Buffer} message
|
|
63
|
+
* @returns {Buffer}
|
|
64
|
+
*/
|
|
65
|
+
function pad(message) {
|
|
66
|
+
const remainder = message.length % BLOCK_SIZE;
|
|
67
|
+
if (remainder === 0) {
|
|
68
|
+
return message;
|
|
69
|
+
}
|
|
70
|
+
const paddingLength = BLOCK_SIZE - remainder;
|
|
71
|
+
const padding = crypto.randomBytes(paddingLength);
|
|
72
|
+
return Buffer.concat([message, padding]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Encrypt message using AES-128-CBC
|
|
77
|
+
* @param {Buffer} message - Message to encrypt
|
|
78
|
+
* @param {Buffer} key - 16 bytes encryption key
|
|
79
|
+
* @returns {Buffer} - Encrypted message
|
|
80
|
+
*/
|
|
81
|
+
function encrypt(message, key) {
|
|
82
|
+
const paddedMessage = pad(message);
|
|
83
|
+
const cipher = crypto.createCipheriv('aes-128-cbc', key, IV);
|
|
84
|
+
cipher.setAutoPadding(false);
|
|
85
|
+
return Buffer.concat([cipher.update(paddedMessage), cipher.final()]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Decrypt message using AES-128-CBC
|
|
90
|
+
* @param {Buffer} message - Encrypted message
|
|
91
|
+
* @param {Buffer} key - 16 bytes encryption key
|
|
92
|
+
* @returns {Buffer} - Decrypted message
|
|
93
|
+
*/
|
|
94
|
+
function decrypt(message, key) {
|
|
95
|
+
const decipher = crypto.createDecipheriv('aes-128-cbc', key, IV);
|
|
96
|
+
decipher.setAutoPadding(false);
|
|
97
|
+
const decrypted = Buffer.concat([decipher.update(message), decipher.final()]);
|
|
98
|
+
|
|
99
|
+
// Remove padding by finding actual command length
|
|
100
|
+
if (decrypted.length > 11) {
|
|
101
|
+
const startMark = decrypted.readUInt16BE(0);
|
|
102
|
+
if (startMark === 0xFE00 || startMark === 0xFE01) {
|
|
103
|
+
const dataLen = decrypted.readUInt16BE(4);
|
|
104
|
+
const cmdLen = dataLen + 11; // header(8) + fcs(1) + end(2)
|
|
105
|
+
if (cmdLen <= decrypted.length) {
|
|
106
|
+
return decrypted.subarray(0, cmdLen);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return decrypted;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse MAC address string to Buffer
|
|
116
|
+
* @param {string} macString - MAC address like "00:12:4B:32:89:BB" or "00124B3289BB"
|
|
117
|
+
* @returns {Buffer} - 6 bytes
|
|
118
|
+
*/
|
|
119
|
+
function parseMacAddress(macString) {
|
|
120
|
+
const hex = macString.replace(/[:-]/g, '');
|
|
121
|
+
if (hex.length !== 12) {
|
|
122
|
+
throw new Error('Invalid MAC address format');
|
|
123
|
+
}
|
|
124
|
+
return Buffer.from(hex, 'hex');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
generateKey,
|
|
129
|
+
encrypt,
|
|
130
|
+
decrypt,
|
|
131
|
+
parseMacAddress,
|
|
132
|
+
IV,
|
|
133
|
+
BLOCK_SIZE,
|
|
134
|
+
};
|