├── .github
└── workflows
│ └── build-truenas.yml
├── .gitignore
├── LICENSE
├── README.md
├── SPECS
├── Makefile
├── kmod-led-ugreen.spec
└── ugreen-led.conf
├── build-scripts
├── debian
│ ├── Dockerfile
│ ├── README.md
│ ├── build-dkms-deb.sh
│ ├── build-utils-deb.sh
│ ├── build.sh
│ └── docker-run.sh
└── truenas
│ ├── Dockerfile
│ ├── README.md
│ ├── build-all.sh
│ ├── build-truenas-kmod.sh
│ └── build.sh
├── cli
├── .clangd
├── Makefile
├── i2c.cpp
├── i2c.h
├── ugreen_leds.cpp
├── ugreen_leds.h
└── ugreen_leds_cli.cpp
├── kmod
├── Makefile
├── dkms.conf
├── led-ugreen.c
└── led-ugreen.h
└── scripts
├── blink-disk.cpp
├── check-standby.cpp
├── systemd
├── ugreen-diskiomon.service
├── ugreen-netdevmon@.service
└── ugreen-probe-leds.service
├── ugreen-diskiomon
├── ugreen-leds.conf
├── ugreen-netdevmon
└── ugreen-probe-leds
/.github/workflows/build-truenas.yml:
--------------------------------------------------------------------------------
1 | name: Build kernel module for TrueNAS
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | schedule:
9 | - cron: "0 0 * * *"
10 |
11 | jobs:
12 | build-and-run:
13 | runs-on: ubuntu-latest
14 |
15 | permissions:
16 | # Give the default GITHUB_TOKEN write permission to commit and push the changed files back to the repository.
17 | contents: write
18 |
19 | steps:
20 | - name: Checkout repository
21 | uses: actions/checkout@v4
22 | with:
23 | ref: gh-actions
24 |
25 | - name: Build the kernel modules
26 | working-directory: build-scripts/truenas
27 | run: bash build-all.sh
28 |
29 | - name: Push results to GitHub
30 | uses: stefanzweifel/git-auto-commit-action@v5
31 | with:
32 | commit_message: "Add compiled modules"
33 | branch: gh-actions
34 | file_pattern: 'build-scripts/truenas/build/*/*/led-ugreen.ko'
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | dx4600_leds_cli
3 | ugreen_leds_cli
4 | run.sh
5 | zpool_leds.sh
6 |
7 | *.mod.c
8 | *.o
9 | *.cmd
10 | *.mod
11 | *.d
12 | modules.order
13 | compile_commands.json
14 |
15 | .cache
16 | Module.symvers
17 | release
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 miskcoo@gmail.com
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | LED Controller for UGREEN's DX/DXP NAS Series
2 | ==
3 |
4 | UGREEN's DX/DXP NAS Series covers 2 to 8 bay NAS devices with a built-in system based on OpenWRT called `UGOS` or Debian called `UGOS-Pro`.
5 | Debian Linux or dedicated NAS operating systems and appliances are compatible with the hardware, but do not have drivers for the LED lights on the front panel to indicate power, network and hard drive activity.
6 | Instead, when using a 3rd party OS with e.g. DX 4600 Pro, only the power indicator light blinks, and the other LEDs are off by default.
7 | For the DXP series, all LEDs blink in rolling sequence when non-UGOS systems are running.
8 |
9 | This repository
10 | - Describes the control logic of UGOS for the LED lights on the device front
11 | - Provides a command-line tool and a kernel module to control them
12 |
13 | For the process of understanding this control logic, please refer to [my blog (in Chinese)](https://blog.miskcoo.com/2024/05/ugreen-dx4600-pro-led-controller).
14 |
15 | > [!NOTE]
16 | > Only tested on the following devices:
17 | > - [x] UGREEN DX4600 Pro
18 | > - [x] UGREEN DX4700+
19 | > - [x] UGREEN DXP2800 (reported in [#19](https://github.com/miskcoo/ugreen_leds_controller/issues/19))
20 | > - [x] UGREEN DXP4800 (confirmed in [#41](https://github.com/miskcoo/ugreen_leds_controller/issues/41))
21 | > - [x] UGREEN DXP4800 Plus (reported [here](https://gist.github.com/Kerryliu/c380bb6b3b69be5671105fc23e19b7e8))
22 | > - [x] UGREEN DXP6800 Pro (reported in [#7](https://github.com/miskcoo/ugreen_leds_controller/issues/7))
23 | > - [x] UGREEN DXP8800 Plus (see [this repo](https://github.com/meyergru/ugreen_dxp8800_leds_controller) and [#1](https://github.com/miskcoo/ugreen_leds_controller/issues/1))
24 | > - [ ] UGREEN DXP480T Plus (**Not yet**, but the protocol has been understood, see [#6](https://github.com/miskcoo/ugreen_leds_controller/issues/6#issuecomment-2156807225))
25 | >
26 | >**I am not sure whether this is compatible with other devices.
27 | >If you have tested it with different devices, please feel free to update the list above!**
28 | >
29 | > Please follow the [Preparation](#Preparation) section to check if the protocol is compatible, and run `./ugreen_leds_cli all` to see which LEDs are supported by this tool.
30 |
31 | For third-party systems, I am using Debian 12 "Bookworm", but you can find some manuals for other systems:
32 | - **DSM**: see [#8](https://github.com/miskcoo/ugreen_leds_controller/issues/8)
33 | - **TrueNAS**: see [#13](https://github.com/miskcoo/ugreen_leds_controller/issues/13) and [this repo](https://github.com/0x556c79/install-ugreen-leds-controller) (and maybe [here](https://github.com/miskcoo/ugreen_leds_controller/tree/truenas-build/build-scripts/truenas)) for how to build the module, and [here](https://gist.github.com/Kerryliu/c380bb6b3b69be5671105fc23e19b7e8) for a script using the cli tool; [here](https://github.com/miskcoo/ugreen_leds_controller/tree/gh-actions/build-scripts/truenas/build) for pre-build drivers
34 | - **unRAID**: there is a [plugin](https://forums.unraid.net/topic/168423-ugreen-nas-led-control/); see also [this repo](https://github.com/ich777/unraid-ugreenleds-driver/tree/master/source/usr/bin)
35 | - **Proxmox**: you need to use the cli tool in Proxmox, not in a VM
36 | - **Debian**: see [the section below](#start-at-boot-for-debian-12)
37 |
38 | Below is an example:
39 | 
40 |
41 | It can be achieved by the following commands:
42 | ```bash
43 | ugreen_leds_cli all -off -status
44 | ugreen_leds_cli power -color 255 0 255 -blink 400 600
45 | sleep 0.1
46 | ugreen_leds_cli netdev -color 255 0 0 -blink 400 600
47 | sleep 0.1
48 | ugreen_leds_cli disk1 -color 255 255 0 -blink 400 600
49 | sleep 0.1
50 | ugreen_leds_cli disk2 -color 0 255 0 -blink 400 600
51 | sleep 0.1
52 | ugreen_leds_cli disk3 -color 0 255 255 -blink 400 600
53 | sleep 0.1
54 | ugreen_leds_cli disk4 -color 0 0 255 -blink 400 600
55 | ```
56 |
57 | ## Preparation
58 |
59 | We communicate with the control chip of the LED via I2C, corresponding to the device with address `0x3a` on *SMBus I801 adapter*.
60 | Before proceeding, we need to load the `i2c-dev` module and install the `i2c-tools` tool.
61 |
62 | ```
63 | $ apt install -y i2c-tools
64 | $ modprobe -v i2c-dev
65 | ```
66 |
67 | Now, we can check if the device located at address `0x3a` of *SMBus I801 adapter* is visible.
68 |
69 | ```
70 | $ i2cdetect -l
71 | i2c-0 i2c Synopsys DesignWare I2C adapter I2C adapter
72 | i2c-1 smbus SMBus I801 adapter at efa0 SMBus adapter
73 | i2c-2 i2c Synopsys DesignWare I2C adapter I2C adapter
74 |
75 | $ i2cdetect -y 1
76 | 0 1 2 3 4 5 6 7 8 9 a b c d e f
77 | 00: 08 -- -- -- -- -- -- --
78 | 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
79 | 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
80 | 30: 30 -- -- -- -- 35 UU UU -- -- 3a -- -- -- -- --
81 | 40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
82 | 50: UU -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
83 | 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
84 | 70: -- -- -- -- -- -- -- --
85 | ```
86 |
87 | ## Build & Usage
88 |
89 | > [!IMPORTANT]
90 | > The command-line tool and the kernel module do conflict.
91 | > To use the command-line tool, you must unload the `led_ugreen` module.
92 |
93 | ### The Command-line Tool
94 |
95 | Use `cd cli && make` to build the command-line tool, and `ugreen_leds_cli` to modify the LED states (requires root permissions).
96 |
97 | ```
98 | Usage: ugreen_leds_cli [LED-NAME...] [-on] [-off] [-(blink|breath) T_ON T_OFF]
99 | [-color R G B] [-brightness BRIGHTNESS] [-status]
100 |
101 | LED_NAME: separated by white space, possible values are
102 | { power, netdev, disk[1-8], all }.
103 | -on / -off: turn on / off corresponding LEDs.
104 | -blink / -breath: set LED to the blink / breath mode. This
105 | mode keeps the LED on for T_ON millseconds and then
106 | keeps it off for T_OFF millseconds.
107 | T_ON and T_OFF should belong to [0, 65535].
108 | -color: set the color of corresponding LEDs.
109 | R, G and B should belong to [0, 255].
110 | -brightness: set the brightness of corresponding LEDs.
111 | BRIGHTNESS should belong to [0, 255].
112 | -status: display the status of corresponding LEDs.
113 | ```
114 |
115 | Below is an example:
116 |
117 | ```bash
118 | # turn on all LEDs
119 | ugreen_leds_cli all -on
120 |
121 | # query LEDs' status
122 | ugreen_leds_cli all -status
123 |
124 | # turn on the power indicator,
125 | # and then set its color to blue,
126 | # and then set its brightness to 128 / 256,
127 | # and finally display its status
128 | ugreen_leds_cli power -on -color 0 0 255 -brightness 128 -status
129 | ```
130 |
131 | ### The Kernel Module
132 |
133 | There are three methods to install the module:
134 |
135 | - **A)** Run `cd kmod && make` to build the kernel module, and then load it with `sudo insmod led-ugreen.ko`.
136 |
137 | - **B)** Alternatively, you can install it with dkms:
138 |
139 | ```bash
140 | cp -r kmod /usr/src/led-ugreen-0.1
141 | dkms add -m led-ugreen -v 0.1
142 | dkms build -m led-ugreen -v 0.1 && dkms install -m led-ugreen -v 0.1
143 | ```
144 |
145 | - **C)** You can also directly install the `.deb` package [here](https://github.com/miskcoo/ugreen_leds_controller/releases).
146 |
147 | After loading the `led-ugreen` module, you need to run `scripts/ugreen-probe-leds`, and you can see LEDs in `/sys/class/leds`.
148 |
149 | Below is an example of setting color, brightness, and blink of the `power` LED:
150 |
151 | ```bash
152 | echo 255 > /sys/class/leds/power/brightness # non-zero brightness turns it on
153 | echo "255 0 0" > /sys/class/leds/power/color # set the color to RGB(255, 0, 0)
154 | echo "blink 100 100" > /sys/class/leds/power/blink_type # blink at 10Hz
155 | ```
156 |
157 | To blink the `netdev` LED when an NIC is active, you can use the `ledtrig-netdev` module (see `scripts/ugreen-netdevmon`):
158 |
159 | ```bash
160 | led="netdev"
161 | modprobe ledtrig-netdev
162 | echo netdev > /sys/class/leds/$led/trigger
163 | echo enp2s0 > /sys/class/leds/$led/device_name
164 | echo 1 > /sys/class/leds/$led/link
165 | echo 1 > /sys/class/leds/$led/tx
166 | echo 1 > /sys/class/leds/$led/rx
167 | echo 100 > /sys/class/leds/$led/interval
168 | ```
169 |
170 | To blink the `disk` LED when a block device is active, you can use the `ledtrig-oneshot` module and monitor the changes of`/sys/block/sda/stat` (see `scripts/ugreen-diskiomon` for an example). If you are using zfs, you can combine this script with that provided in [#1](https://github.com/miskcoo/ugreen_leds_controller/issues/1) to change the LED's color when a disk drive failure occurs.
171 | To see how to map the disk LEDs to correct disk slots, please read the [Disk Mapping](#disk-mapping) section.
172 |
173 | #### Start at Boot (for Debian 12)
174 |
175 | The configure file of `ugreen-diskiomon` and `ugreen-netdevmon` is `/etc/ugreen-led.conf`.
176 | Please see `scripts/ugreen-leds.conf` for an example.
177 |
178 | - Add the following lines to `/etc/modules-load.d/ugreen-led.conf`
179 | ```
180 | cat > /etc/modules-load.d/ugreen-led.conf << EOF
181 | i2c-dev
182 | led-ugreen
183 | ledtrig-oneshot
184 | ledtrig-netdev
185 | EOF
186 | ```
187 |
188 | - Install the `smartctl` tool: `apt install smartmontools`
189 |
190 | - Install the kernel module by one of the three methods mentioned above.
191 | For example, directly install [the deb package](https://github.com/miskcoo/ugreen_leds_controller/releases).
192 |
193 | - Copy files in the `scripts` directory:
194 | ```bash
195 | # copy the scripts
196 | scripts=(ugreen-diskiomon ugreen-netdevmon ugreen-probe-leds)
197 | for f in ${scripts[@]}; do
198 | chmod +x "scripts/$f"
199 | cp "scripts/$f" /usr/bin
200 | done
201 |
202 | # copy the configuration file, you can change it if needed
203 | cp scripts/ugreen-leds.conf /etc/ugreen-leds.conf
204 |
205 | # copy the systemd services
206 | cp scripts/systemd/*.service /etc/systemd/system/
207 |
208 | systemctl daemon-reload
209 |
210 | # change enp2s0 to the network device you want to monitor
211 | systemctl start ugreen-netdevmon@enp2s0
212 | systemctl start ugreen-diskiomon
213 |
214 | # if you confirm that everything works well,
215 | # run the command below to make the service start at boot
216 | systemctl enable ugreen-netdevmon@enp2s0
217 | systemctl enable ugreen-diskiomon
218 | ```
219 |
220 | - (_Optional_) To reduce the CPU usage of blinking LEDs when disks are active, you can enter the `scripts` directory and do the following things:
221 | ```bash
222 | # compile the disk activities monitor
223 | g++ -std=c++17 -O2 blink-disk.cpp -o ugreen-blink-disk
224 |
225 | # copy the binary file (the path can be changed, see BLINK_MON_PATH in ugreen-leds.conf)
226 | cp ugreen-blink-disk /usr/bin
227 | ```
228 |
229 | - (_Optional_) Similarly, to reduce the latency of the standby check, you can enter the `scripts` directory and do the following things:
230 | ```bash
231 | # compile the disk standby checker
232 | g++ -std=c++17 -O2 check-standby.cpp -o ugreen-check-standby
233 |
234 | # copy the binary file (the path can be changed, see STANDBY_MON_PATH in ugreen-leds.conf)
235 | cp ugreen-check-standby /usr/bin
236 | ```
237 |
238 | ## Disk Mapping
239 |
240 | To make the disk LEDs useful, we should map the disk LEDs to correct disk slots. First of all, we should highlight that using `/dev/sdX` is never a smart idea, as it may change at every boot.
241 | In the script `ugreen-diskiomon` we provide three mapping methods: **by ATA**, **by HCTL** and **by serial**.
242 |
243 | The best mapping method is using serial numbers, but it needs to record them manually and fill the `DISK_SERIAL` array in `/etc/ugreen-leds.conf`. We use ATA mapping by default, and find that UGOS also uses a similar mapping method (see [#15](https://github.com/miskcoo/ugreen_leds_controller/pull/15)).
244 | See the comments in `scripts/ugreen-leds.conf` for more details.
245 |
246 | The HCTL mapping depends on how the SATA controllers are connected to the PCIe bus and the disk slots. To check the HCTL order, you can run the following command, and check the serial of your disks:
247 |
248 | ```bash
249 | # lsblk -S -x hctl -o name,hctl,serial
250 | NAME HCTL SERIAL
251 | sda 0:0:0:0 XXKEREXX
252 | sdc 1:0:0:0 XXKG2BXX
253 | sdb 2:0:0:0 XXGMU6XX
254 | sdd 3:0:0:0 XXKJEZXX
255 | sde 4:0:0:0 XXKJHBXX
256 | sdf 5:0:0:0 XXGT2ZXX
257 | sdg 6:0:0:0 XXKH3SXX
258 | sdh 7:0:0:0 XXJDB1XX
259 | ```
260 | > [!NOTE]
261 | > As far as we know, the mapping between HCTL and the disk serial are stable at each boot (see [#4](https://github.com/miskcoo/ugreen_leds_controller/pull/4) and [#9](https://github.com/miskcoo/ugreen_leds_controller/issues/9)).
262 | > However, it has been reported that the exact order is model-dependent (see [#9](https://github.com/miskcoo/ugreen_leds_controller/issues/9)).
263 | > - For DX4600 Pro and DXP8800 Plus, the mapping is `X:0:0:0 -> diskX`.
264 | > - For DXP6800 Pro, `0:0:0:0` and `1:0:0:0` are mapped to `disk5` and `disk6`, and `2:0:0:0` to `6:0:0:0` are mapped to `disk1` to `disk4`.
265 | >
266 | > The script will use `dmidecode` to detect the device model, but I suggest to check the mapping outputed by the script manually.
267 |
268 | ## Communication Protocols
269 |
270 | The IDs for the LED lights on the front panel of the NAS chassis are as follows:
271 |
272 | | ID | LED |
273 | |-------------|--------------------------------|
274 | | `0` | Power indicator |
275 | | `1` | Network device indicator |
276 | | `2`,`3`,... | Hard drive indicator "disk1", "disk2" etc. |
277 |
278 | ### Query Status
279 |
280 | Reading 11 bytes from the address `0x81 + LED_ID` allows us to obtain the current status of the corresponding LED. The meaning of these 11 bytes is as follows:
281 |
282 | | Address | Meaning of Corresponding Data |
283 | |---------|--------------------------------|
284 | | `0x00` | LED status: 0 (off), 1 (on), 2 (blink), 3 (breath) |
285 | | `0x01` | LED brightness |
286 | | `0x02` | LED color (Red component in RGB) |
287 | | `0x03` | LED color (Green component in RGB) |
288 | | `0x04` | LED color (Blue component in RGB) |
289 | | `0x05` | Milliseconds needed to complete one blink/breath cycle (high 8 bits) |
290 | | `0x06` | Milliseconds needed to complete one blink/breath cycle (low 8 bits) |
291 | | `0x07` | Milliseconds the LED is on during one blink/breath cycle (high 8 bits) |
292 | | `0x08` | Milliseconds the LED is on during one blink/breath cycle (low 8 bits) |
293 | | `0x09` | Checksum of data in the range 0x00 - 0x08 (high 8 bits) |
294 | | `0x0a` | Checksum of data in the range 0x00 - 0x08 (low 8 bits) |
295 |
296 | The checksum is a 16-bit value obtained by summing all the data at the corresponding positions as unsigned integers.
297 |
298 | We can directly use `i2cget` to read from the relevant registers. For example, below is the status of the power indicator light (purple, blinking once per second, lit for 40% of the time, with a brightness of 180/256):
299 |
300 | ```
301 | $ i2cget -y 0x01 0x3a 0x81 i 0x0b 0x02 0xb4 0xff 0x00 0xff 0x03 0xe8 0x01 0x90 0x04 0x30
302 | ```
303 |
304 | ### Change Status
305 |
306 | By writing 12 bytes to the address `0x00 + LED_ID`, we can modify the current status of the corresponding LED. The meaning of these 12 bytes is as follows:
307 |
308 | | Address | Meaning of Corresponding Data |
309 | |---------|--------------------------------|
310 | | `0x00` | LED ID |
311 | | `0x01` | Constant: 0xa0 |
312 | | `0x02` | Constant: 0x01 |
313 | | `0x03` | Constant: 0x00 |
314 | | `0x04` | Constant: 0x00 |
315 | | `0x05` | If the value is 1, it indicates modifying brightness
If the value is 2, it indicates modifying color
If the value is 3, it indicates setting the on/off state
If the value is 4, it indicates setting the blink state
If the value is 5, it indicates setting the breath state |
316 | | `0x06` | First parameter |
317 | | `0x07` | Second parameter |
318 | | `0x08` | Third parameter |
319 | | `0x09` | Fourth parameter |
320 | | `0x0a` | Checksum of data in the range 0x01 - 0x09 (high 8 bits) |
321 | | `0x0b` | Checksum of data in the range 0x01 - 0x09 (low 8 bits) |
322 |
323 | For the four different modification types at address `0x05`:
324 | - If we need to modify **brightness**, the first parameter contains brightness information.
325 | - If we need to modify **color**, the first three parameters represent RGB information.
326 | - If we need to toggle the **on/off state**, the first parameter is either 0 (off) or 1 (on).
327 | - If we need to set the **blink/breath state**, the first two parameters together form a 16-bit unsigned integer in big-endian order, representing the number of milliseconds needed to complete one blink/breath cycle. The next two parameters, also in big-endian order, represent the time in milliseconds the LED is on during one blink/breath cycle.
328 |
329 | Below is an example for turning off and on the power indicator light using `i2cset`:
330 |
331 | ```
332 | # turn off power LED
333 | $ i2cset -y 0x01 0x3a 0x00 0x00 0xa0 0x01 0x00 0x00 0x03 0x01 0x00 0x00 0x00 0x00 0xa5 i
334 |
335 | # turn on power LED
336 | $ i2cset -y 0x01 0x3a 0x00 0x00 0xa0 0x01 0x00 0x00 0x03 0x00 0x00 0x00 0x00 0x00 0xa4 i
337 | ```
338 |
339 | ## Acknowledgement
340 |
341 | ChatGPT, [this V2EX post](https://fast.v2ex.com/t/991429), Ghidra
342 |
--------------------------------------------------------------------------------
/SPECS/Makefile:
--------------------------------------------------------------------------------
1 | # If KERNELRELEASE is defined, the make command using this Makefile has
2 | # been invoked by the kernel build system and so can use its language.
3 | # Otherwise, if KERNELRELEASE is null, a make command was issued from
4 | # the command line. So invoke the kernel build system.
5 |
6 | ifeq ($(KERNELRELEASE),)
7 |
8 | # KVERSION should be set in the environment if this
9 | # build is not for the currently running kernel.
10 | KVERSION ?= $(shell uname -r)
11 |
12 | # BUILD_DIR should be set in the environment if a
13 | # subdirectory of /lib/modules/ is not appropriate.
14 | BUILD_DIR ?= /lib/modules/${KVERSION}/build
15 |
16 | PWD := $(shell pwd)
17 |
18 | all:
19 | $(MAKE) -C $(BUILD_DIR) M=$(PWD) modules
20 |
21 | modules:
22 | $(MAKE) -C $(BUILD_DIR) M=$(PWD) modules
23 |
24 | modules_install:
25 | $(MAKE) -C $(BUILD_DIR) M=$(PWD) modules_install
26 |
27 | clean:
28 | $(MAKE) -C $(BUILD_DIR) M=$(PWD) clean
29 | rm -rf *~ *.o .*.cmd *.mod.c *.ko *.ko.unsigned .depend \
30 | .tmp_versions modules.order Module.symvers Module.markers
31 |
32 | .PHONY: modules modules_install clean
33 |
34 | else
35 |
36 | # Called from kernel build system -- just declare the module(s).
37 |
38 | obj-m += led-ugreen.o
39 |
40 | endif
41 |
--------------------------------------------------------------------------------
/SPECS/kmod-led-ugreen.spec:
--------------------------------------------------------------------------------
1 | # Define the kmod package name here.
2 | %define kmod_name led-ugreen
3 |
4 | # If kmod_kernel_version isn't defined on the rpmbuild line, define it here.
5 | %{!?kmod_kernel_version: %define kmod_kernel_version 5.14.0-427.20.1.el9_4}
6 |
7 | %{!?dist: %define dist .el9}
8 |
9 | Name: kmod-%{kmod_name}
10 | Version: 0.1
11 | Release: 17%{?dist}
12 | Summary: %{kmod_name} kernel module(s)
13 | Group: System Environment/Kernel
14 | License: GPLv2
15 | URL: https://github.com/miskcoo/ugreen_leds_controller
16 |
17 | # Sources.
18 | #tar -czf ugreen_leds_controller.tar.gz ugreen_leds_controller
19 | Source0: %{kmod_name}-%{version}.tar.gz
20 |
21 | # Fix for the SB-signing issue caused by a bug in /usr/lib/rpm/brp-strip
22 | # https://bugzilla.redhat.com/show_bug.cgi?id=1967291
23 |
24 | %define __spec_install_post \
25 | /usr/lib/rpm/check-buildroot \
26 | /usr/lib/rpm/redhat/brp-ldconfig \
27 | /usr/lib/rpm/brp-compress \
28 | /usr/lib/rpm/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump \
29 | /usr/lib/rpm/brp-strip-static-archive /usr/bin/strip \
30 | /usr/lib/rpm/brp-python-bytecompile "" "1" "0" \
31 | /usr/lib/rpm/brp-python-hardlink \
32 | /usr/lib/rpm/redhat/brp-mangle-shebangs
33 |
34 | # Source code patches
35 |
36 | %define findpat %( echo "%""P" )
37 | %define dup_state_dir %{_localstatedir}/lib/rpm-state/kmod-dups
38 | %define kver_state_dir %{dup_state_dir}/kver
39 | %define kver_state_file %{kver_state_dir}/%{kmod_kernel_version}.%{_arch}
40 | %define dup_module_list %{dup_state_dir}/rpm-kmod-%{kmod_name}-modules
41 | %define debug_package %{nil}
42 |
43 | %global _use_internal_dependency_generator 0
44 | %global kernel_source() %{_usrsrc}/kernels/%{kmod_kernel_version}.%{_arch}
45 |
46 | BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
47 |
48 | ExclusiveArch: x86_64
49 |
50 | BuildRequires: elfutils-libelf-devel
51 | BuildRequires: kernel-devel = %{kmod_kernel_version}
52 | BuildRequires: kernel-abi-stablelists
53 | BuildRequires: kernel-rpm-macros
54 | BuildRequires: redhat-rpm-config
55 | BuildRequires: systemd-units
56 | BuildRequires: gcc-c++
57 |
58 | Provides: kernel-modules >= %{kmod_kernel_version}.%{_arch}
59 | Provides: kmod-%{kmod_name} = %{?epoch:%{epoch}:}%{version}-%{release}
60 |
61 | Requires(post): %{_sbindir}/weak-modules
62 | Requires(postun): %{_sbindir}/weak-modules
63 | Requires: kernel >= %{kmod_kernel_version}
64 | Requires: kernel-core-uname-r >= %{kmod_kernel_version}
65 | Requires: i2c-tools smartmontools dmidecode
66 |
67 | %description
68 | This package provides the %{kmod_name} kernel module(s) for ugreen_leds_controller.
69 | It is built to depend upon the specific ABI provided by a range of releases
70 | of the same variant of the Linux kernel and not on any one specific build.
71 |
72 |
73 | %prep
74 | %setup -q -n ugreen_leds_controller
75 | echo "override %{kmod_name} * weak-updates/%{kmod_name}" > kmod-%{kmod_name}.conf
76 |
77 | # Apply patch(es)
78 |
79 | %build
80 | pushd kmod
81 | %{__make} -C %{kernel_source} %{?_smp_mflags} modules M=$PWD
82 | popd
83 |
84 | pushd scripts
85 | %{__cxx} -std=c++17 -O2 blink-disk.cpp -o ugreen-blink-disk
86 | %{__cxx} -std=c++17 -O2 check-standby.cpp -o ugreen-check-standby
87 | popd
88 |
89 | whitelist="/lib/modules/kabi-current/kabi_stablelist_%{_target_cpu}"
90 | for modules in $( find . -name "*.ko" -type f -printf "%{findpat}\n" | sed 's|\.ko$||' | sort -u ) ; do
91 | # update greylist
92 | nm -u ./$modules.ko | sed 's/.*U //' | sed 's/^\.//' | sort -u | while read -r symbol; do
93 | grep -q "^\s*$symbol\$" $whitelist || echo "$symbol" >> ./greylist
94 | done
95 | done
96 | sort -u greylist | uniq > greylist.txt
97 |
98 | %install
99 | %{__install} -d %{buildroot}/lib/modules/%{kmod_kernel_version}.%{_arch}/extra/%{kmod_name}/
100 | %{__install} kmod/%{kmod_name}.ko %{buildroot}/lib/modules/%{kmod_kernel_version}.%{_arch}/extra/%{kmod_name}/
101 | %{__install} -d %{buildroot}%{_sysconfdir}/depmod.d/
102 | %{__install} -m 0644 kmod-%{kmod_name}.conf %{buildroot}%{_sysconfdir}/depmod.d/
103 | %{__install} -d %{buildroot}%{_defaultdocdir}/kmod-%{kmod_name}-%{version}/
104 | %{__install} -m 0644 greylist.txt %{buildroot}%{_defaultdocdir}/kmod-%{kmod_name}-%{version}/
105 | %{__install} -m 0644 scripts/ugreen-leds.conf %{buildroot}%{_sysconfdir}/
106 |
107 | mkdir -p %{buildroot}/etc/modules-load.d/
108 | %{__install} -m 0644 SPECS/ugreen-led.conf %{buildroot}/etc/modules-load.d/
109 |
110 | mkdir -p %{buildroot}%{_bindir}/
111 |
112 | %{__install} -m 0755 scripts/ugreen-diskiomon %{buildroot}%{_bindir}/
113 | %{__install} -m 0755 scripts/ugreen-netdevmon %{buildroot}%{_bindir}/
114 | %{__install} -m 0755 scripts/ugreen-probe-leds %{buildroot}%{_bindir}/
115 | %{__install} -m 0755 scripts/ugreen-blink-disk %{buildroot}%{_bindir}/
116 | %{__install} -m 0755 scripts/ugreen-check-standby %{buildroot}%{_bindir}/
117 |
118 | mkdir -p %{buildroot}%{_unitdir}/
119 | %{__install} -m 0644 scripts/ugreen-netdevmon@.service %{buildroot}%{_unitdir}/
120 | %{__install} -m 0644 scripts/ugreen-diskiomon.service %{buildroot}%{_unitdir}/
121 |
122 | # strip the modules(s)
123 | find %{buildroot} -type f -name \*.ko -exec %{__strip} --strip-debug \{\} \;
124 |
125 | # Sign the modules(s)
126 | %if %{?_with_modsign:1}%{!?_with_modsign:0}
127 | # If the module signing keys are not defined, define them here.
128 | %{!?privkey: %define privkey %{_sysconfdir}/pki/SECURE-BOOT-KEY.priv}
129 | %{!?pubkey: %define pubkey %{_sysconfdir}/pki/SECURE-BOOT-KEY.der}
130 | for module in $(find %{buildroot} -type f -name \*.ko);
131 | do %{_usrsrc}/kernels/%{kmod_kernel_version}.%{_arch}/scripts/sign-file \
132 | sha256 %{privkey} %{pubkey} $module;
133 | done
134 | %endif
135 |
136 | %clean
137 | %{__rm} -rf %{buildroot}
138 |
139 | %post
140 | modules=( $(find /lib/modules/%{kmod_kernel_version}.x86_64/extra/%{kmod_name} | grep '\.ko$') )
141 | printf '%s\n' "${modules[@]}" | %{_sbindir}/weak-modules --add-modules --no-initramfs
142 |
143 | mkdir -p "%{kver_state_dir}"
144 | touch "%{kver_state_file}"
145 |
146 | echo "systemctl start ugreen-diskiomon.service"
147 | echo "ls /sys/class/leds"
148 | echo "Make sure you can see disk1, netdev, power, etc."
149 | echo "systemctl enable ugreen-diskiomon.service"
150 | echo
151 | echo "to uninstall:"
152 | echo "systemctl stop ugreen-diskiomon.service"
153 | echo "systemctl disable ugreen-diskiomon.service"
154 |
155 | exit 0
156 |
157 | %posttrans
158 | # We have to re-implement part of weak-modules here because it doesn't allow
159 | # calling initramfs regeneration separately
160 | if [ -f "%{kver_state_file}" ]; then
161 | kver_base="%{kmod_kernel_version}"
162 | kvers=$(ls -d "/lib/modules/${kver_base%%.*}"*)
163 |
164 | for k_dir in $kvers; do
165 | k="${k_dir#/lib/modules/}"
166 |
167 | tmp_initramfs="/boot/initramfs-$k.tmp"
168 | dst_initramfs="/boot/initramfs-$k.img"
169 |
170 | # The same check as in weak-modules: we assume that the kernel present
171 | # if the symvers file exists.
172 | if [ -e "/$k_dir/symvers.gz" ]; then
173 | /usr/bin/dracut -f "$tmp_initramfs" "$k" || exit 1
174 | cmp -s "$tmp_initramfs" "$dst_initramfs"
175 | if [ "$?" = 1 ]; then
176 | mv "$tmp_initramfs" "$dst_initramfs"
177 | else
178 | rm -f "$tmp_initramfs"
179 | fi
180 | fi
181 | done
182 |
183 | rm -f "%{kver_state_file}"
184 | rmdir "%{kver_state_dir}" 2> /dev/null
185 | fi
186 |
187 | rmdir "%{dup_state_dir}" 2> /dev/null
188 |
189 | exit 0
190 |
191 | %preun
192 | if rpm -q --filetriggers kmod 2> /dev/null| grep -q "Trigger for weak-modules call on kmod removal"; then
193 | mkdir -p "%{kver_state_dir}"
194 | touch "%{kver_state_file}"
195 | fi
196 |
197 | mkdir -p "%{dup_state_dir}"
198 | rpm -ql kmod-%{kmod_name}-%{version}-%{release}.%{_arch} | grep '\.ko$' > "%{dup_module_list}"
199 |
200 | %postun
201 | if rpm -q --filetriggers kmod 2> /dev/null| grep -q "Trigger for weak-modules call on kmod removal"; then
202 | initramfs_opt="--no-initramfs"
203 | else
204 | initramfs_opt=""
205 | fi
206 |
207 | modules=( $(cat "%{dup_module_list}") )
208 | rm -f "%{dup_module_list}"
209 | printf '%s\n' "${modules[@]}" | %{_sbindir}/weak-modules --remove-modules $initramfs_opt
210 |
211 | rmdir "%{dup_state_dir}" 2> /dev/null
212 |
213 | exit 0
214 |
215 | %files
216 | %defattr(644,root,root,755)
217 | /lib/modules/%{kmod_kernel_version}.%{_arch}/
218 | %config /etc/depmod.d/kmod-%{kmod_name}.conf
219 | %config(noreplace) %{_sysconfdir}/ugreen-leds.conf
220 | %config(noreplace) /etc/modules-load.d/ugreen-led.conf
221 | %doc /usr/share/doc/kmod-%{kmod_name}-%{version}/
222 | %attr(0755, root, root) %{_bindir}/ugreen-diskiomon
223 | %attr(0755, root, root) %{_bindir}/ugreen-netdevmon
224 | %attr(0755, root, root) %{_bindir}/ugreen-probe-leds
225 | %attr(0755, root, root) %{_bindir}/ugreen-blink-disk
226 | %attr(0755, root, root) %{_bindir}/ugreen-check-standby
227 | %{_unitdir}/ugreen-netdevmon@.service
228 | %{_unitdir}/ugreen-diskiomon.service
229 |
230 | %changelog
231 | * Sat Jun 22 2024 Axel Olmos - 0.0-14
232 | - Rewritten to be a ugreen-led kmod spec. Thank you Akemi for making the original template!
233 |
234 | * Fri Nov 26 2021 Akemi Yagi - 0.0-1
235 | - Initial build for RHEL 9
236 |
--------------------------------------------------------------------------------
/SPECS/ugreen-led.conf:
--------------------------------------------------------------------------------
1 | i2c-dev
2 | led-ugreen
3 | ledtrig-oneshot
4 | ledtrig-netdev
5 |
--------------------------------------------------------------------------------
/build-scripts/debian/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:bookworm
2 |
3 | RUN set -ex \
4 | && sed -i -- 's/Types: deb/Types: deb deb-src/g' /etc/apt/sources.list.d/debian.sources \
5 | && apt-get update \
6 | && apt-get install -y --no-install-recommends \
7 | build-essential \
8 | cdbs \
9 | devscripts \
10 | equivs \
11 | fakeroot \
12 | wget \
13 | git \
14 | && apt-get clean \
15 | && rm -rf /tmp/* /var/tmp/*
16 |
17 |
18 |
--------------------------------------------------------------------------------
/build-scripts/debian/README.md:
--------------------------------------------------------------------------------
1 |
2 | Build it in docker:
3 |
4 | ```
5 | bash docker-run.sh
6 | ```
7 |
8 | The outputs can be found in `./build` directory.
9 |
--------------------------------------------------------------------------------
/build-scripts/debian/build-dkms-deb.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | set -e
4 | set -x
5 |
6 | pkgver="0.3"
7 | pkgname="led-ugreen-dkms"
8 | drivername="led-ugreen"
9 |
10 | mkdir -p $pkgname/DEBIAN
11 |
12 | cat < $pkgname/DEBIAN/control
13 | Package: $pkgname
14 | Version: $pkgver
15 | Architecture: amd64
16 | Maintainer: Yuhao Zhou
17 | Depends: dkms
18 | Homepage: https://github.com/miskcoo/ugreen_leds_controller
19 | Description: UGREEN NAS LED driver
20 | A reverse-engineered LED driver of UGREEN NAS.
21 | EOF
22 |
23 | cat < $pkgname/DEBIAN/postinst
24 | #!/usr/bin/bash
25 |
26 | dkms add -m $drivername -v $pkgver
27 | dkms build -m $drivername -v $pkgver && dkms install -m $drivername -v $pkgver || true
28 |
29 | EOF
30 |
31 | cat < $pkgname/DEBIAN/prerm
32 | #!/usr/bin/bash
33 |
34 | dkms remove -m $drivername -v $pkgver --all || true
35 | EOF
36 |
37 | chmod +x $pkgname/DEBIAN/postinst
38 | chmod +x $pkgname/DEBIAN/prerm
39 |
40 |
41 | # dkms files
42 | mkdir -p $pkgname/usr/src/$drivername-$pkgver
43 |
44 | kmod_files=(kmod/Makefile kmod/dkms.conf kmod/led-ugreen.c kmod/led-ugreen.h kmod/Makefile)
45 | for f in ${kmod_files[@]}; do
46 | cp -rv $f $pkgname/usr/src/$drivername-$pkgver/
47 | done
48 |
49 | # change to root
50 | chown -R root:root $pkgname/
51 |
52 | dpkg -b $pkgname
53 |
54 | rm -rv $pkgname
55 |
56 |
--------------------------------------------------------------------------------
/build-scripts/debian/build-utils-deb.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | set -e
4 | set -x
5 |
6 | pkgver="0.3"
7 | pkgname="led-ugreen-utils"
8 | drivername="led-ugreen"
9 |
10 | mkdir -p $pkgname/DEBIAN
11 |
12 | cat < $pkgname/DEBIAN/control
13 | Package: $pkgname
14 | Version: $pkgver
15 | Architecture: amd64
16 | Maintainer: Yuhao Zhou
17 | Depends: dmidecode, smartmontools
18 | Homepage: https://github.com/miskcoo/ugreen_leds_controller
19 | Description: UGREEN NAS LED tools
20 | A reverse-engineered LED tools of UGREEN NAS.
21 | EOF
22 |
23 |
24 | # scripts
25 | mkdir -p $pkgname/usr/bin/
26 |
27 | script_files=(ugreen-probe-leds ugreen-netdevmon ugreen-diskiomon)
28 |
29 | for f in ${script_files[@]}; do
30 | cp scripts/$f $pkgname/usr/bin/
31 | chmod +x $pkgname/usr/bin/$f
32 | done
33 |
34 | # systemd file
35 | mkdir -p $pkgname/etc/systemd/system
36 | cp scripts/systemd/*.service $pkgname/etc/systemd/system/
37 | # cp scripts/ugreen-ledmon@.service $pkgname/etc/systemd/system/
38 |
39 | # example config file
40 | cp scripts/ugreen-leds.conf $pkgname/etc/ugreen-leds.example.conf
41 |
42 | # compile the disk activities monitor
43 | g++ -std=c++17 -O2 scripts/blink-disk.cpp -o ugreen-blink-disk
44 | cp ugreen-blink-disk $pkgname/usr/bin
45 |
46 | # compile the disk standby monitor
47 | #g++ -std=c++17 -O2 scripts/check-standby.cpp -o ugreen-check-standby
48 | #cp ugreen-check-standby $pkgname/usr/bin
49 |
50 | # change to root
51 | chown -R root:root $pkgname/
52 |
53 | # cli
54 | cd cli && make -j 4
55 | cd ..
56 | cp cli/ugreen_leds_cli $pkgname/usr/bin
57 | # cp cli/ugreen_daemon $pkgname/usr/bin
58 | chmod +x $pkgname/usr/bin
59 |
60 | dpkg -b $pkgname
61 |
62 | rm -rv $pkgname
63 |
64 |
--------------------------------------------------------------------------------
/build-scripts/debian/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/bash
2 |
3 | set -x
4 |
5 | git clone https://github.com/miskcoo/ugreen_leds_controller.git
6 | cd ugreen_leds_controller
7 |
8 | if [ ! -z $1 ]; then
9 | git checkout $1
10 | fi
11 |
12 | bash build-scripts/debian/build-dkms-deb.sh
13 | bash build-scripts/debian/build-utils-deb.sh
14 | dpkg-name led-ugreen-dkms.deb
15 | dpkg-name led-ugreen-utils.deb
16 | mv *.deb ..
17 |
--------------------------------------------------------------------------------
/build-scripts/debian/docker-run.sh:
--------------------------------------------------------------------------------
1 |
2 |
3 | set -x
4 |
5 | if [[ ! -d ./build ]]; then
6 | mkdir build
7 | fi
8 |
9 | docker build --tag bookworm-build .
10 | docker run \
11 | --rm \
12 | --mount type=bind,source=$(pwd)/build,target=/build \
13 | --mount type=bind,source=$(pwd)/build.sh,target=/build.sh \
14 | bookworm-build \
15 | bash -c "cd /build && bash /build.sh $1"
16 |
--------------------------------------------------------------------------------
/build-scripts/truenas/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:bookworm
2 |
3 | RUN set -ex \
4 | && sed -i -- 's/Types: deb/Types: deb deb-src/g' /etc/apt/sources.list.d/debian.sources \
5 | && apt-get update \
6 | && apt-get install -y --no-install-recommends \
7 | build-essential \
8 | cdbs \
9 | devscripts \
10 | equivs \
11 | fakeroot \
12 | wget \
13 | git \
14 | && apt-get clean \
15 | && rm -rf /tmp/* /var/tmp/*
16 |
17 |
18 |
--------------------------------------------------------------------------------
/build-scripts/truenas/README.md:
--------------------------------------------------------------------------------
1 |
2 | Run `bash build.sh TrueNAS-SCALE-Dragonfish/24.04.1`
3 |
4 | ### Important Note
5 | When you find errors like this:
6 | ```
7 | --2025-01-19 09:03:41-- https://download.truenas.com/TrueNAS-SCALE-ElectricEel/24.10.1/packages/Packages.gz
8 | Resolving download.truenas.com (download.truenas.com)... 185.244.226.2
9 | Connecting to download.truenas.com (download.truenas.com)|185.244.226.2|:443... connected.
10 | HTTP request sent, awaiting response... 404 Not Found
11 | 2025-01-19 09:03:42 ERROR 404: Not Found.
12 | ```
13 |
14 | You can try to use previous versions. See Issue #28.
15 |
--------------------------------------------------------------------------------
/build-scripts/truenas/build-all.sh:
--------------------------------------------------------------------------------
1 |
2 | truenas_versions=$(curl https://download.truenas.com/ | grep -Po "(?<=href=\")TrueNAS-SCALE-\w*?/\d+(\.\d+)*" | uniq)
3 |
4 | for version in ${truenas_versions}; do
5 | # only build for TrueNAS-SCALE-Dragonfish/24.04.0 and later versions
6 | if [ $(echo $version | grep -Po "(?<=/)\d+") -ge 24 ] && [ ! -d "build/$version" ]; then
7 | echo Building "$version"
8 | bash build.sh "$version"
9 | fi
10 | done
11 |
12 | find -name *.ko
13 |
--------------------------------------------------------------------------------
/build-scripts/truenas/build-truenas-kmod.sh:
--------------------------------------------------------------------------------
1 | set -x
2 |
3 |
4 | url_prefix="https://download.truenas.com/$1/packages/"
5 |
6 | mkdir truenas_working
7 | cd truenas_working
8 |
9 | if ! wget "${url_prefix}Packages.gz"; then
10 | cd ..
11 | rm -rf truenas_working
12 | exit 0
13 | fi
14 |
15 | gzip -d Packages.gz
16 | deb_name=$(grep linux-headers-truenas-production Packages | grep -Po "(?<=Filename: ./).*deb")
17 | wget "${url_prefix}${deb_name}"
18 | mkdir tmp
19 | dpkg-deb -R ${deb_name} tmp
20 |
21 | git clone https://github.com/miskcoo/ugreen_dx4600_leds_controller.git
22 | cd ugreen_dx4600_leds_controller/kmod
23 |
24 | cat < Makefile
25 | TARGET = led-ugreen
26 | obj-m += led-ugreen.o
27 | ccflags-y := -std=gnu11
28 |
29 | all:
30 | make -C ../../tmp/usr/src/$(ls ../../tmp/usr/src/) M=$(pwd) modules
31 |
32 | EOF
33 |
34 | make
35 |
36 | cd ../../../
37 | mkdir -p $1
38 | cp truenas_working/ugreen_dx4600_leds_controller/kmod/*.ko $1
39 | rm -fr truenas_working
40 |
--------------------------------------------------------------------------------
/build-scripts/truenas/build.sh:
--------------------------------------------------------------------------------
1 | set -x
2 |
3 | if [[ ! -d ./build ]]; then
4 | mkdir build
5 | fi
6 |
7 | docker build --tag bookworm-build .
8 | docker run \
9 | --rm \
10 | --mount type=bind,source=$(pwd)/build,target=/build \
11 | --mount type=bind,source=$(pwd)/build-truenas-kmod.sh,target=/build.sh \
12 | bookworm-build \
13 | bash -c "cd /build && bash /build.sh $1"
14 |
--------------------------------------------------------------------------------
/cli/.clangd:
--------------------------------------------------------------------------------
1 | CompileFlags:
2 | Add: [-std=c++17]
3 |
--------------------------------------------------------------------------------
/cli/Makefile:
--------------------------------------------------------------------------------
1 |
2 | CC = g++
3 | CFLAGS = -I. -O2 -Wall -static
4 | DEPS = i2c.h ugreen_leds.h
5 | OBJ = i2c.o ugreen_leds.o
6 |
7 | %.o: %.cpp $(DEPS)
8 | $(CC) -c -o $@ $< $(CFLAGS)
9 |
10 | ugreen_leds_cli: $(OBJ) ugreen_leds_cli.o
11 | $(CC) -o $@ $^ $(CFLAGS)
12 |
--------------------------------------------------------------------------------
/cli/i2c.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | #include "i2c.h"
10 |
11 |
12 | i2c_device_t::~i2c_device_t() {
13 | if (_fd) close(_fd);
14 | }
15 |
16 | int i2c_device_t::start(const char *filename, uint16_t addr) {
17 | _fd = open(filename, O_RDWR);
18 |
19 | if (_fd < 0) {
20 | int rc = _fd;
21 | _fd = 0;
22 | return rc;
23 | }
24 |
25 | int rc = ioctl(_fd, I2C_SLAVE, addr);
26 | if (rc < 0) {
27 | close(_fd);
28 | _fd = 0;
29 | return rc;
30 | }
31 |
32 | return 0;
33 | };
34 |
35 | std::vector i2c_device_t::read_block_data(uint8_t command, uint32_t size) {
36 | if (!_fd) return { };
37 |
38 | if (size > I2C_SMBUS_BLOCK_MAX)
39 | return { };
40 |
41 | i2c_smbus_data smbus_data;
42 | smbus_data.block[0] = size;
43 |
44 | i2c_smbus_ioctl_data ioctl_data;
45 | ioctl_data.size = I2C_SMBUS_I2C_BLOCK_DATA;
46 | ioctl_data.read_write = I2C_SMBUS_READ;
47 | ioctl_data.command = command;
48 | ioctl_data.data = &smbus_data;
49 |
50 | int rc = ioctl(_fd, I2C_SMBUS, &ioctl_data);
51 |
52 | if (rc < 0) return { };
53 |
54 | std::vector data;
55 | for (uint32_t i = 0; i < size; ++i)
56 | data.push_back(smbus_data.block[i + 1]);
57 |
58 | return data;
59 | }
60 |
61 | int i2c_device_t::write_block_data(uint8_t command, std::vector data) {
62 | if (!_fd) return -1;
63 |
64 | uint32_t size = data.size();
65 | if (size > I2C_SMBUS_BLOCK_MAX)
66 | size = I2C_SMBUS_BLOCK_MAX;
67 |
68 | i2c_smbus_data smbus_data;
69 | smbus_data.block[0] = size;
70 | for (uint32_t i = 0; i < size; ++i)
71 | smbus_data.block[i + 1] = data[i];
72 |
73 | i2c_smbus_ioctl_data ioctl_data;
74 | ioctl_data.size = I2C_SMBUS_I2C_BLOCK_DATA;
75 | ioctl_data.read_write = I2C_SMBUS_WRITE;
76 | ioctl_data.command = command;
77 | ioctl_data.data = &smbus_data;
78 |
79 | int rc = ioctl(_fd, I2C_SMBUS, &ioctl_data);
80 |
81 | return rc;
82 | }
83 |
84 | uint8_t i2c_device_t::read_byte_data(uint8_t command) {
85 | if (!_fd) return { };
86 |
87 | i2c_smbus_data smbus_data;
88 |
89 | i2c_smbus_ioctl_data ioctl_data;
90 | ioctl_data.size = I2C_SMBUS_BYTE_DATA;
91 | ioctl_data.read_write = I2C_SMBUS_READ;
92 | ioctl_data.command = command;
93 | ioctl_data.data = &smbus_data;
94 |
95 | int rc = ioctl(_fd, I2C_SMBUS, &ioctl_data);
96 |
97 | if (rc < 0) return { };
98 |
99 | return smbus_data.byte & 0xff;
100 | }
101 |
--------------------------------------------------------------------------------
/cli/i2c.h:
--------------------------------------------------------------------------------
1 | #ifndef __UGREEN_I2C_H__
2 | #define __UGREEN_I2C_H__
3 |
4 | #include
5 | #include
6 |
7 | class i2c_device_t {
8 |
9 | private:
10 | int _fd;
11 |
12 | public:
13 | ~i2c_device_t();
14 |
15 | int start(const char *filename, uint16_t addr);
16 | std::vector read_block_data(uint8_t command, uint32_t size);
17 | int write_block_data(uint8_t command, std::vector data);
18 | uint8_t read_byte_data(uint8_t command);
19 |
20 | };
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/cli/ugreen_leds.cpp:
--------------------------------------------------------------------------------
1 | #include "ugreen_leds.h"
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #define I2C_DEV_PATH "/sys/class/i2c-dev/"
8 |
9 | int ugreen_leds_t::start() {
10 | namespace fs = std::filesystem;
11 |
12 | if (!fs::exists(I2C_DEV_PATH))
13 | return -1;
14 |
15 | for (const auto& entry : fs::directory_iterator(I2C_DEV_PATH)) {
16 | if (entry.is_directory()) {
17 | std::ifstream ifs(entry.path() / "device/name");
18 | std::string line;
19 | std::getline(ifs, line);
20 |
21 | if (line.rfind("SMBus I801 adapter", 0) == 0) {
22 | const auto i2c_dev = "/dev/" + entry.path().filename().string();
23 | return _i2c.start(i2c_dev.c_str(), UGREEN_LED_I2C_ADDR);
24 | }
25 | }
26 | }
27 |
28 | return -1;
29 | }
30 |
31 | static int compute_checksum(const std::vector& data, int size) {
32 | if (size < 2 || size > (int)data.size())
33 | return 0;
34 |
35 | int sum = 0;
36 | for (int i = 0; i < size; ++i)
37 | sum += (int)data[i];
38 |
39 | return sum;
40 | }
41 |
42 | static bool verify_checksum(const std::vector& data) {
43 | int size = data.size();
44 | if (size < 2) return false;
45 | int sum = compute_checksum(data, size - 2);
46 | return sum != 0 && sum == (data[size - 1] | (((int)data[size - 2]) << 8));
47 | }
48 |
49 | static void append_checksum(std::vector& data) {
50 | int size = data.size();
51 | int sum = compute_checksum(data, size);
52 | data.push_back((sum >> 8) & 0xff);
53 | data.push_back(sum & 0xff);
54 | }
55 |
56 | ugreen_leds_t::led_data_t ugreen_leds_t::get_status(led_type_t id) {
57 | led_data_t data { };
58 | data.is_available = false;
59 |
60 | auto raw_data = _i2c.read_block_data(0x81 + (uint8_t)id, 0xb);
61 | if (raw_data.size() != 0xb || !verify_checksum(raw_data))
62 | return data;
63 |
64 | switch (raw_data[0]) {
65 | case 0: data.op_mode = op_mode_t::off; break;
66 | case 1: data.op_mode = op_mode_t::on; break;
67 | case 2: data.op_mode = op_mode_t::blink; break;
68 | case 3: data.op_mode = op_mode_t::breath; break;
69 | default: return data;
70 | };
71 |
72 |
73 | data.brightness = raw_data[1];
74 | data.color_r = raw_data[2];
75 | data.color_g = raw_data[3];
76 | data.color_b = raw_data[4];
77 | int t_hight = (((int)raw_data[5]) << 8) | raw_data[6];
78 | int t_low = (((int)raw_data[7]) << 8) | raw_data[8];
79 | data.t_on = t_low;
80 | data.t_off = t_hight - t_low;
81 | data.is_available = true;
82 |
83 | return data;
84 | }
85 |
86 | int ugreen_leds_t::_change_status(led_type_t id, uint8_t command, std::array, 4> params) {
87 | std::vector data {
88 | // 3c 3b 3a
89 | 0x00, 0xa0, 0x01,
90 | // 39 38 37
91 | 0x00, 0x00, command,
92 | // 36 - 33
93 | params[0].value_or(0x00),
94 | params[1].value_or(0x00),
95 | params[2].value_or(0x00),
96 | params[3].value_or(0x00),
97 | };
98 |
99 | append_checksum(data);
100 | data[0] = (uint8_t)id;
101 | return _i2c.write_block_data((uint8_t)id, data);
102 | }
103 |
104 | int ugreen_leds_t::set_onoff(led_type_t id, uint8_t status) {
105 | if (status >= 2) return -1;
106 | return _change_status(id, 0x03, { status } );
107 | }
108 |
109 | int ugreen_leds_t::_set_blink_or_breath(uint8_t command, led_type_t id, uint16_t t_on, uint16_t t_off) {
110 | uint16_t t_hight = t_on + t_off;
111 | uint16_t t_low = t_on;
112 | return _change_status(id, command, {
113 | (uint8_t)(t_hight >> 8),
114 | (uint8_t)(t_hight & 0xff),
115 | (uint8_t)(t_low >> 8),
116 | (uint8_t)(t_low & 0xff),
117 | } );
118 | }
119 |
120 | int ugreen_leds_t::set_rgb(led_type_t id, uint8_t r, uint8_t g, uint8_t b) {
121 | return _change_status(id, 0x02, { r, g, b } );
122 | }
123 |
124 | int ugreen_leds_t::set_brightness(led_type_t id, uint8_t brightness) {
125 | return _change_status(id, 0x01, { brightness } );
126 | }
127 |
128 | bool ugreen_leds_t::is_last_modification_successful() {
129 | return _i2c.read_byte_data(0x80) == 1;
130 | }
131 |
132 | int ugreen_leds_t::set_blink(led_type_t id, uint16_t t_on, uint16_t t_off) {
133 | return _set_blink_or_breath(0x04, id, t_on, t_off);
134 | }
135 |
136 | int ugreen_leds_t::set_breath(led_type_t id, uint16_t t_on, uint16_t t_off) {
137 | return _set_blink_or_breath(0x05, id, t_on, t_off);
138 | }
139 |
--------------------------------------------------------------------------------
/cli/ugreen_leds.h:
--------------------------------------------------------------------------------
1 | #ifndef __UGREEN_LEDS_H__
2 | #define __UGREEN_LEDS_H__
3 |
4 | #include
5 | #include
6 |
7 | #include "i2c.h"
8 |
9 | #define UGREEN_LED_POWER ugreen_leds_t::led_type_t::power
10 | #define UGREEN_LED_NETDEV ugreen_leds_t::led_type_t::netdev
11 | #define UGREEN_LED_DISK1 ugreen_leds_t::led_type_t::disk1
12 | #define UGREEN_LED_DISK2 ugreen_leds_t::led_type_t::disk2
13 | #define UGREEN_LED_DISK3 ugreen_leds_t::led_type_t::disk3
14 | #define UGREEN_LED_DISK4 ugreen_leds_t::led_type_t::disk4
15 | #define UGREEN_LED_DISK5 ugreen_leds_t::led_type_t::disk5
16 | #define UGREEN_LED_DISK6 ugreen_leds_t::led_type_t::disk6
17 | #define UGREEN_LED_DISK7 ugreen_leds_t::led_type_t::disk7
18 | #define UGREEN_LED_DISK8 ugreen_leds_t::led_type_t::disk8
19 |
20 | // #define UGREEN_LED_I2C_DEV "/dev/i2c-1"
21 | #define UGREEN_LED_I2C_ADDR 0x3a
22 |
23 | class ugreen_leds_t {
24 |
25 | i2c_device_t _i2c;
26 |
27 | public:
28 |
29 | enum class op_mode_t : uint8_t {
30 | off = 0, on, blink, breath
31 | };
32 |
33 | enum class led_type_t : uint8_t {
34 | power = 0, netdev, disk1, disk2, disk3, disk4, disk5, disk6, disk7, disk8
35 | };
36 |
37 | struct led_data_t {
38 | bool is_available;
39 | op_mode_t op_mode;
40 | uint8_t brightness;
41 | uint8_t color_r, color_g, color_b;
42 | uint16_t t_on, t_off;
43 |
44 | };
45 |
46 | public:
47 | int start();
48 |
49 | led_data_t get_status(led_type_t id);
50 | int set_onoff(led_type_t id, uint8_t status);
51 | int set_rgb(led_type_t id, uint8_t r, uint8_t g, uint8_t b);
52 | int set_brightness(led_type_t id, uint8_t brightness);
53 | int set_blink(led_type_t id, uint16_t t_on, uint16_t t_off);
54 | int set_breath(led_type_t id, uint16_t t_on, uint16_t t_off);
55 |
56 | bool is_last_modification_successful();
57 |
58 | private:
59 | int _set_blink_or_breath(uint8_t command, led_type_t id, uint16_t t_on, uint16_t t_off);
60 | int _change_status(led_type_t id, uint8_t command, std::array, 4> params);
61 | };
62 |
63 |
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/cli/ugreen_leds_cli.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include