Inital Commit
All checks were successful
UGREEN NAS / UGREEN NAS [amd64] (push) Successful in 26s

This commit is contained in:
Cantibra
2025-10-26 21:11:06 +01:00
commit c93c7b0784
31 changed files with 2629 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
CC = g++
CFLAGS = -I. -O2 -Wall
DEPS = i2c.h main.h
OBJ = i2c.o main.o
%.o: %.cpp $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
ugreen-led-cli: $(OBJ) ugreen-led-cli.o
$(CC) -o $@ $^ $(CFLAGS)

View File

@@ -0,0 +1,96 @@
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include "i2c.h"
i2c_device_t::~i2c_device_t() {
if (_fd) close(_fd);
}
int i2c_device_t::start(const char * filename, uint16_t addr) {
_fd = open(filename, O_RDWR);
if (_fd < 0) {
int rc = _fd;
_fd = 0;
return rc;
}
int rc = ioctl(_fd, I2C_SLAVE, addr);
if (rc < 0) {
close(_fd);
_fd = 0;
return rc;
}
return 0;
};
std::vector < uint8_t > i2c_device_t::read_block_data(uint8_t command, uint32_t size) {
if (!_fd) return {};
if (size > I2C_SMBUS_BLOCK_MAX)
return {};
i2c_smbus_data smbus_data;
smbus_data.block[0] = size;
i2c_smbus_ioctl_data ioctl_data;
ioctl_data.size = I2C_SMBUS_I2C_BLOCK_DATA;
ioctl_data.read_write = I2C_SMBUS_READ;
ioctl_data.command = command;
ioctl_data.data = & smbus_data;
int rc = ioctl(_fd, I2C_SMBUS, & ioctl_data);
if (rc < 0) return {};
std::vector < uint8_t > data;
for (uint32_t i = 0; i < size; ++i)
data.push_back(smbus_data.block[i + 1]);
return data;
}
int i2c_device_t::write_block_data(uint8_t command, std::vector < uint8_t > data) {
if (!_fd) return -1;
uint32_t size = data.size();
if (size > I2C_SMBUS_BLOCK_MAX)
size = I2C_SMBUS_BLOCK_MAX;
i2c_smbus_data smbus_data;
smbus_data.block[0] = size;
for (uint32_t i = 0; i < size; ++i)
smbus_data.block[i + 1] = data[i];
i2c_smbus_ioctl_data ioctl_data;
ioctl_data.size = I2C_SMBUS_I2C_BLOCK_DATA;
ioctl_data.read_write = I2C_SMBUS_WRITE;
ioctl_data.command = command;
ioctl_data.data = & smbus_data;
int rc = ioctl(_fd, I2C_SMBUS, & ioctl_data);
return rc;
}
uint8_t i2c_device_t::read_byte_data(uint8_t command) {
if (!_fd) return {};
i2c_smbus_data smbus_data;
i2c_smbus_ioctl_data ioctl_data;
ioctl_data.size = I2C_SMBUS_BYTE_DATA;
ioctl_data.read_write = I2C_SMBUS_READ;
ioctl_data.command = command;
ioctl_data.data = & smbus_data;
int rc = ioctl(_fd, I2C_SMBUS, & ioctl_data);
if (rc < 0) return {};
return smbus_data.byte & 0xff;
}

View File

@@ -0,0 +1,20 @@
#ifndef __UGREEN_I2C_H__
#define __UGREEN_I2C_H__
#include <stdint.h>
#include <vector>
class i2c_device_t {
private:
int _fd;
public:
~i2c_device_t();
int start(const char * filename, uint16_t addr);
std::vector < uint8_t > read_block_data(uint8_t command, uint32_t size);
int write_block_data(uint8_t command, std::vector < uint8_t > data);
uint8_t read_byte_data(uint8_t command);
};
#endif

View File

@@ -0,0 +1,136 @@
#include "main.h"
#include <string>
#include <filesystem>
#include <fstream>
#include <iostream>
#define I2C_DEV_PATH "/sys/class/i2c-dev/"
int ugreen_leds_t::start() {
namespace fs = std::filesystem;
if (!fs::exists(I2C_DEV_PATH))
return -1;
for (const auto & entry: fs::directory_iterator(I2C_DEV_PATH)) {
if (entry.is_directory()) {
std::ifstream ifs(entry.path() / "device/name");
std::string line;
std::getline(ifs, line);
if (line.rfind("SMBus I801 adapter", 0) == 0) {
const auto i2c_dev = "/dev/" + entry.path().filename().string();
return _i2c.start(i2c_dev.c_str(), UGREEN_LED_I2C_ADDR);
}
}
}
return -1;
}
static int compute_checksum(const std::vector < uint8_t > & data, int size) {
if (size < 2 || size > (int) data.size())
return 0;
int sum = 0;
for (int i = 0; i < size; ++i)
sum += (int) data[i];
return sum;
}
static bool verify_checksum(const std::vector < uint8_t > & data) {
int size = data.size();
if (size < 2) return false;
int sum = compute_checksum(data, size - 2);
return sum != 0 && sum == (data[size - 1] | (((int) data[size - 2]) << 8));
}
static void append_checksum(std::vector < uint8_t > & data) {
int size = data.size();
int sum = compute_checksum(data, size);
data.push_back((sum >> 8) & 0xff);
data.push_back(sum & 0xff);
}
ugreen_leds_t::led_data_t ugreen_leds_t::get_status(led_type_t id) {
led_data_t data {};
data.is_available = false;
auto raw_data = _i2c.read_block_data(0x81 + (uint8_t) id, 0xb);
if (raw_data.size() != 0xb || !verify_checksum(raw_data))
return data;
switch (raw_data[0]) {
case 0: data.op_mode = op_mode_t::off; break;
case 1: data.op_mode = op_mode_t::on; break;
case 2: data.op_mode = op_mode_t::blink; break;
case 3: data.op_mode = op_mode_t::breath; break;
default: return data;
};
data.brightness = raw_data[1];
data.color_r = raw_data[2];
data.color_g = raw_data[3];
data.color_b = raw_data[4];
int t_hight = (((int) raw_data[5]) << 8) | raw_data[6];
int t_low = (((int) raw_data[7]) << 8) | raw_data[8];
data.t_on = t_low;
data.t_off = t_hight - t_low;
data.is_available = true;
return data;
}
int ugreen_leds_t::_change_status(led_type_t id, uint8_t command, std::array < std::optional < uint8_t > , 4 > params) {
std::vector < uint8_t > data {
0x00, 0xa0, 0x01,
0x00, 0x00, command,
params[0].value_or(0x00),
params[1].value_or(0x00),
params[2].value_or(0x00),
params[3].value_or(0x00),
};
append_checksum(data);
data[0] = (uint8_t) id;
return _i2c.write_block_data((uint8_t) id, data);
}
int ugreen_leds_t::set_onoff(led_type_t id, uint8_t status) {
if (status >= 2) return -1;
return _change_status(id, 0x03, {
status
});
}
int ugreen_leds_t::_set_blink_or_breath(uint8_t command, led_type_t id, uint16_t t_on, uint16_t t_off) {
uint16_t t_hight = t_on + t_off;
uint16_t t_low = t_on;
return _change_status(id, command, {
(uint8_t)(t_hight >> 8),
(uint8_t)(t_hight & 0xff),
(uint8_t)(t_low >> 8),
(uint8_t)(t_low & 0xff),
});
}
int ugreen_leds_t::set_rgb(led_type_t id, uint8_t r, uint8_t g, uint8_t b) {
return _change_status(id, 0x02, { r, g, b } );
}
int ugreen_leds_t::set_brightness(led_type_t id, uint8_t brightness) {
return _change_status(id, 0x01, { brightness } );
}
bool ugreen_leds_t::is_last_modification_successful() {
return _i2c.read_byte_data(0x80) == 1;
}
int ugreen_leds_t::set_blink(led_type_t id, uint16_t t_on, uint16_t t_off) {
return _set_blink_or_breath(0x04, id, t_on, t_off);
}
int ugreen_leds_t::set_breath(led_type_t id, uint16_t t_on, uint16_t t_off) {
return _set_blink_or_breath(0x05, id, t_on, t_off);
}

View File

@@ -0,0 +1,58 @@
#ifndef __UGREEN_LEDS_H__
#define __UGREEN_LEDS_H__
#include <array>
#include <optional>
#include "i2c.h"
#define UGREEN_LED_POWER ugreen_leds_t::led_type_t::power
#define UGREEN_LED_NETDEV ugreen_leds_t::led_type_t::netdev
#define UGREEN_LED_DISK1 ugreen_leds_t::led_type_t::disk1
#define UGREEN_LED_DISK2 ugreen_leds_t::led_type_t::disk2
#define UGREEN_LED_DISK3 ugreen_leds_t::led_type_t::disk3
#define UGREEN_LED_DISK4 ugreen_leds_t::led_type_t::disk4
#define UGREEN_LED_DISK5 ugreen_leds_t::led_type_t::disk5
#define UGREEN_LED_DISK6 ugreen_leds_t::led_type_t::disk6
#define UGREEN_LED_DISK7 ugreen_leds_t::led_type_t::disk7
#define UGREEN_LED_DISK8 ugreen_leds_t::led_type_t::disk8
// #define UGREEN_LED_I2C_DEV "/dev/i2c-1"
#define UGREEN_LED_I2C_ADDR 0x3a
class ugreen_leds_t {
i2c_device_t _i2c;
public:
enum class op_mode_t: uint8_t {
off = 0, on, blink, breath
};
enum class led_type_t: uint8_t {
power = 0, netdev, disk1, disk2, disk3, disk4, disk5, disk6, disk7, disk8
};
struct led_data_t {
bool is_available;
op_mode_t op_mode;
uint8_t brightness;
uint8_t color_r, color_g, color_b;
uint16_t t_on, t_off;
};
public:
int start();
led_data_t get_status(led_type_t id);
int set_onoff(led_type_t id, uint8_t status);
int set_rgb(led_type_t id, uint8_t r, uint8_t g, uint8_t b);
int set_brightness(led_type_t id, uint8_t brightness);
int set_blink(led_type_t id, uint16_t t_on, uint16_t t_off);
int set_breath(led_type_t id, uint16_t t_on, uint16_t t_off);
bool is_last_modification_successful();
private:
int _set_blink_or_breath(uint8_t command, led_type_t id, uint16_t t_on, uint16_t t_off);
int _change_status(led_type_t id, uint8_t command, std::array < std::optional < uint8_t > , 4 > params);
};
#endif

View File

@@ -0,0 +1,288 @@
#include <unistd.h>
#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <map>
#include <functional>
#include "main.h"
#define MAX_RETRY_COUNT 5
#define USLEEP_READ_STATUS_INTERVAL 8000
#define USLEEP_READ_STATUS_RETRY_INTERVAL 3000
#define USLEEP_MODIFICATION_INTERVAL 500
#define USLEEP_MODIFICATION_RETRY_INTERVAL 3000
#define USLEEP_MODIFICATION_QUERY_RESULT_INTERVAL 2000
static std::map<std::string, ugreen_leds_t::led_type_t> led_name_map = {
{ "power", UGREEN_LED_POWER },
{ "netdev", UGREEN_LED_NETDEV },
{ "disk1", UGREEN_LED_DISK1 },
{ "disk2", UGREEN_LED_DISK2 },
{ "disk3", UGREEN_LED_DISK3 },
{ "disk4", UGREEN_LED_DISK4 },
{ "disk5", UGREEN_LED_DISK5 },
{ "disk6", UGREEN_LED_DISK6 },
{ "disk7", UGREEN_LED_DISK7 },
{ "disk8", UGREEN_LED_DISK8 },
};
using led_type_pair = std::pair < std::string, ugreen_leds_t::led_type_t > ;
ugreen_leds_t::led_data_t get_status_robust(ugreen_leds_t & leds_controller, ugreen_leds_t::led_type_t led) {
usleep(USLEEP_READ_STATUS_INTERVAL);
auto data = leds_controller.get_status(led);
for (int retry_cnt = 1; !data.is_available && retry_cnt < MAX_RETRY_COUNT; ++retry_cnt) {
usleep(USLEEP_READ_STATUS_RETRY_INTERVAL);
data = leds_controller.get_status(led);
}
return data;
}
void show_leds_info(ugreen_leds_t & leds_controller,
const std::vector < led_type_pair > & leds) {
for (auto led: leds) {
auto data = get_status_robust(leds_controller, led.second);
if (!data.is_available) {
std::printf("%s: unavailable or non-existent\n", led.first.c_str());
continue;
}
std::string op_mode_txt = "unknown";
switch (data.op_mode) {
case ugreen_leds_t::op_mode_t::off:
op_mode_txt = "off";
break;
case ugreen_leds_t::op_mode_t::on:
op_mode_txt = "on";
break;
case ugreen_leds_t::op_mode_t::blink:
op_mode_txt = "blink";
break;
case ugreen_leds_t::op_mode_t::breath:
op_mode_txt = "breath";
break;
};
std::printf("%s: status = %s, brightness = %d, color = RGB(%d, %d, %d)",
led.first.c_str(), op_mode_txt.c_str(), (int) data.brightness,
(int) data.color_r, (int) data.color_g, (int) data.color_b);
if (data.op_mode == ugreen_leds_t::op_mode_t::blink) {
std::printf(", blink_on = %d ms, blink_off = %d ms",
(int) data.t_on, (int) data.t_off);
}
std::puts("");
}
}
void show_help() {
std::cerr <<
"Usage: ugreen-led-cli [LED-NAME...] [-on] [-off] [-(blink|breath) T_ON T_OFF] [-color R G B] [-brightness BRIGHTNESS] [-status]\n\n"
" LED_NAME: Separated by white space, possible values are { power, netdev, disk[1-8], all }.\n\n"
" -on / -off: Turn ON / OFF corresponding LEDs.\n\n"
" -blink / -breath: set LED to the blink / breath mode. This mode keeps the LED on for T_ON millseconds\n"
" and then keeps it off for T_OFF millseconds.\n"
" T_ON and T_OFF should belong to [0, 65535].\n\n"
" -color: Set the color of corresponding LEDs.\n"
" R, G and B should belong to [0, 255].\n\n"
" -brightness: Set the brightness of corresponding LEDs.\n"
" BRIGHTNESS should belong to [0, 255].\n\n"
" -status: Display the status of corresponding LEDs.\n" <<
std::endl;
}
void show_help_and_exit() {
show_help();
std::exit(-1);
}
ugreen_leds_t::led_type_t get_led_type(const std::string & name) {
if (led_name_map.find(name) == led_name_map.end()) {
std::cerr << "Err: unknown LED name " << name << std::endl;
show_help_and_exit();
}
return led_name_map[name];
}
int parse_integer(const std::string & str, int low = 0, int high = 0xffff) {
std::size_t size;
int x = std::stoi(str, & size);
if (size != str.size()) {
std::cerr << "Err: " << str << " is not an integer." << std::endl;
show_help_and_exit();
}
if (x < low || x > high) {
std::cerr << "Err: " << str << " is not in [" << low << ", " << high << "]" << std::endl;
show_help_and_exit();
}
return x;
}
int main(int argc, char * argv[]) {
if (argc < 2) {
show_help();
return 0;
}
ugreen_leds_t leds_controller;
if (leds_controller.start() != 0) {
std::cerr << "The command-line tool and the kernel module do conflict." << std::endl;
std::cerr << "To use the command-line tool, you must unload the led_ugreen module." << std::endl;
return -1;
}
std::deque < std::string > args;
for (int i = 1; i < argc; ++i)
args.emplace_back(argv[i]);
// parse LED names
std::vector < led_type_pair > leds;
while (!args.empty() && args.front().front() != '-') {
if (args.front() == "all") {
for (const auto & v: led_name_map) {
if (get_status_robust(leds_controller, v.second).is_available)
leds.push_back(v);
}
} else {
auto led_type = get_led_type(args.front());
leds.emplace_back(args.front(), led_type);
}
args.pop_front();
}
// if no additional parameters, display current info
if (args.empty()) {
show_leds_info(leds_controller, leds);
return 0;
}
// (is_modification, callback)
using ops_pair = std::pair < bool, std:: function < int(led_type_pair) >> ;
std::vector < ops_pair > ops_seq;
while (!args.empty()) {
if (args.front() == "-on" || args.front() == "-off") {
// turn on / off LEDs
uint8_t status = args.front() == "-on";
ops_seq.emplace_back(true, [ = , & leds_controller](led_type_pair led) {
return leds_controller.set_onoff(led.second, status);
});
args.pop_front();
} else if (args.front() == "-blink" || args.front() == "-breath") {
// set blink
bool is_blink = (args.front() == "-blink");
args.pop_front();
if (args.size() < 2) {
std::cerr << "Err: -blink / -breath requires 2 parameters" << std::endl;
show_help_and_exit();
}
uint16_t t_on = parse_integer(args.front(), 0x0000, 0xffff);
args.pop_front();
uint16_t t_off = parse_integer(args.front(), 0x0000, 0xffff);
args.pop_front();
ops_seq.emplace_back(true, [ = , & leds_controller](led_type_pair led) {
if (is_blink) {
return leds_controller.set_blink(led.second, t_on, t_off);
} else {
return leds_controller.set_breath(led.second, t_on, t_off);
}
});
} else if (args.front() == "-color") {
// set color
args.pop_front();
if (args.size() < 3) {
std::cerr << "Err: -color requires 3 parameters" << std::endl;
show_help_and_exit();
}
uint8_t R = parse_integer(args.front(), 0x00, 0xff);
args.pop_front();
uint8_t G = parse_integer(args.front(), 0x00, 0xff);
args.pop_front();
uint8_t B = parse_integer(args.front(), 0x00, 0xff);
args.pop_front();
ops_seq.emplace_back(true, [ = , & leds_controller](led_type_pair led) {
return leds_controller.set_rgb(led.second, R, G, B);
});
} else if (args.front() == "-brightness") {
// set brightness
args.pop_front();
if (args.size() < 1) {
std::cerr << "Err: -brightness requires 1 parameter" << std::endl;
show_help_and_exit();
}
uint8_t brightness = parse_integer(args.front(), 0x00, 0xff);
args.pop_front();
ops_seq.emplace_back(true, [ = , & leds_controller](led_type_pair led) {
return leds_controller.set_brightness(led.second, brightness);
});
} else if (args.front() == "-status") {
// display the status
args.pop_front();
ops_seq.emplace_back(false, [ = , & leds_controller](led_type_pair led) {
show_leds_info(leds_controller, {
led
});
return 0;
});
} else {
std::cerr << "Err: unknown parameter " << args.front() << std::endl;
show_help_and_exit();
}
}
for (const auto & led: leds) {
for (const auto & fn_pair: ops_seq) {
bool is_modification = fn_pair.first;
const auto & fn = fn_pair.second;
int last_status = -1;
for (int retry_cnt = 0; retry_cnt < MAX_RETRY_COUNT && last_status != 0; ++retry_cnt) {
if (retry_cnt == 0) {
if (is_modification)
usleep(USLEEP_MODIFICATION_INTERVAL); // usleep_range(200, 0x5dc)
} else {
usleep(USLEEP_MODIFICATION_RETRY_INTERVAL);
}
last_status = fn(led);
if (last_status == 0 && is_modification) {
usleep(USLEEP_MODIFICATION_QUERY_RESULT_INTERVAL);
last_status = !leds_controller.is_last_modification_successful();
}
}
if (last_status != 0) {
std::cerr << "failed to change status!" << std::endl;
return -1;
}
}
}
return 0;
}