Skip to content

Commit 4e14f03

Browse files
Wer-WolfCalcProgrammer1
authored andcommitted
Add support for Arctic RGB Controller
The Arctic RGB controller support 4 RGB channel and can be controlled over a CH341 USB-to-serial chip. The controller support two commands, one for identifying the controller on a serial port and one for setting the RGB values for each RGB channel. Since the controllers disables the RGB channels after ~1s, a keepalive thread is used.
1 parent fa52f4d commit 4e14f03

File tree

6 files changed

+405
-0
lines changed

6 files changed

+405
-0
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*-----------------------------------------*\
2+
| ArcticController.h |
3+
| |
4+
| Controller Interface for Arctic devices |
5+
| |
6+
| Armin Wolf (Wer-Wolf) 01/09/2023 |
7+
\*-----------------------------------------*/
8+
9+
#include "ArcticController.h"
10+
#include <cstring>
11+
12+
using namespace std::chrono_literals;
13+
14+
#define ARCTIC_COMMAND_SET_RGB 0x00
15+
#define ARCTIC_COMMAND_IDENTIFY 0x5C
16+
17+
#define ARCTIC_RESPONSE_BUFFER_LENGTH 15
18+
#define ARCTIC_RESPONSE_COMMAND_OFFSET 0
19+
#define ARCTIC_RESPONSE_DATA_OFFSET 1
20+
#define ARCTIC_RESPONSE_DATA_LENGTH 12
21+
#define ARCTIC_RESPONSE_XOR_CSUM_OFFSET 13
22+
#define ARCTIC_RESPONSE_ADD_CSUM_OFFSET 14
23+
24+
#define ARCTIC_COMMAND_BUFFER_LENGTH(payload_size) (sizeof(header) + 1 + payload_size)
25+
#define ARCTIC_COMMAND_COMMAND_OFFSET (sizeof(header))
26+
#define ARCTIC_COMMAND_PAYLOAD_OFFSET (sizeof(header) + 1)
27+
28+
const unsigned char header[] =
29+
{
30+
0x01,
31+
0x02,
32+
0x03,
33+
0xFF,
34+
0x05,
35+
0xFF,
36+
0x02,
37+
0x03
38+
};
39+
40+
const unsigned char identify_payload[] =
41+
{
42+
0x01,
43+
0xFE,
44+
0x01,
45+
0xFE
46+
};
47+
48+
ArcticController::ArcticController(const std::string &portname)
49+
: port_name(portname),
50+
serialport(portname.c_str(), 250000, SERIAL_PORT_PARITY_NONE, SERIAL_PORT_SIZE_8, SERIAL_PORT_STOP_BITS_2, false)
51+
{
52+
serialport.serial_set_dtr(true);
53+
}
54+
55+
ArcticController::~ArcticController()
56+
{
57+
serialport.serial_set_dtr(false);
58+
}
59+
60+
static void FormatCommandBuffer(char *buffer, char command)
61+
{
62+
std::memcpy(buffer, header, sizeof(header));
63+
buffer[ARCTIC_COMMAND_COMMAND_OFFSET] = command;
64+
}
65+
66+
void ArcticController::SetChannels(std::vector<RGBColor> colors)
67+
{
68+
char buffer[ARCTIC_COMMAND_BUFFER_LENGTH(colors.size() * 3)];
69+
70+
FormatCommandBuffer(buffer, ARCTIC_COMMAND_SET_RGB);
71+
72+
for(unsigned int channel = 0; channel < colors.size(); channel++)
73+
{
74+
const unsigned int offset = ARCTIC_COMMAND_PAYLOAD_OFFSET + channel * 3;
75+
76+
buffer[offset + 0x00] = std::min<unsigned int>(254, RGBGetRValue(colors[channel]));
77+
buffer[offset + 0x01] = std::min<unsigned int>(254, RGBGetGValue(colors[channel]));
78+
buffer[offset + 0x02] = std::min<unsigned int>(254, RGBGetBValue(colors[channel]));
79+
}
80+
81+
serialport.serial_write(buffer, sizeof(buffer));
82+
}
83+
84+
static char XORChecksum(char *data, int length)
85+
{
86+
char sum = 0;
87+
88+
for(int i = 0; i < length; i++)
89+
{
90+
sum ^= data[i];
91+
}
92+
93+
return sum;
94+
}
95+
96+
static char AddChecksum(char *data, int length)
97+
{
98+
char sum = 0;
99+
100+
for(int i = 0; i < length; i++)
101+
{
102+
sum = (char)(sum + data[i]);
103+
}
104+
105+
return sum;
106+
}
107+
108+
bool ArcticController::IsPresent()
109+
{
110+
char buffer[ARCTIC_COMMAND_BUFFER_LENGTH(sizeof(identify_payload))];
111+
char response[ARCTIC_RESPONSE_BUFFER_LENGTH];
112+
int ret;
113+
114+
FormatCommandBuffer(buffer, ARCTIC_COMMAND_IDENTIFY);
115+
std::memcpy(buffer + ARCTIC_COMMAND_PAYLOAD_OFFSET, identify_payload, sizeof(identify_payload));
116+
117+
serialport.serial_flush_rx();
118+
ret = serialport.serial_write(buffer, sizeof(buffer));
119+
if(ret != sizeof(buffer))
120+
{
121+
return false;
122+
}
123+
124+
std::this_thread::sleep_for(100ms);
125+
126+
ret = serialport.serial_read(response, sizeof(response));
127+
if(ret != sizeof(response))
128+
{
129+
return false;
130+
}
131+
132+
if(response[ARCTIC_RESPONSE_COMMAND_OFFSET] != ARCTIC_COMMAND_IDENTIFY)
133+
{
134+
return false;
135+
}
136+
137+
if(response[ARCTIC_RESPONSE_XOR_CSUM_OFFSET] != XORChecksum(&response[ARCTIC_RESPONSE_DATA_OFFSET], ARCTIC_RESPONSE_DATA_LENGTH))
138+
{
139+
return false;
140+
}
141+
142+
if(response[ARCTIC_RESPONSE_ADD_CSUM_OFFSET] != AddChecksum(&response[ARCTIC_RESPONSE_DATA_OFFSET], ARCTIC_RESPONSE_DATA_LENGTH))
143+
{
144+
return false;
145+
}
146+
147+
return true;
148+
}
149+
150+
std::string ArcticController::GetLocation()
151+
{
152+
return port_name;
153+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*-----------------------------------------*\
2+
| ArcticController.h |
3+
| |
4+
| Controller Interface for Arctic devices |
5+
| |
6+
| Armin Wolf (Wer-Wolf) 01/09/2023 |
7+
\*-----------------------------------------*/
8+
9+
#pragma once
10+
#include "RGBController.h"
11+
#include "serial_port.h"
12+
13+
class ArcticController
14+
{
15+
public:
16+
ArcticController(const std::string &portname);
17+
~ArcticController();
18+
19+
void SetChannels(std::vector<RGBColor> colors);
20+
bool IsPresent();
21+
22+
std::string GetLocation();
23+
24+
private:
25+
std::string port_name;
26+
serial_port serialport;
27+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*-----------------------------------------*\
2+
| ArcticControllerDetect.cpp |
3+
| |
4+
| Detect Arctic RGB controllers |
5+
| |
6+
| Armin Wolf (Wer-Wolf) 01/09/2023 |
7+
\*-----------------------------------------*/
8+
9+
#include "Detector.h"
10+
#include "ArcticController.h"
11+
#include "RGBController.h"
12+
#include "RGBController_Arctic.h"
13+
#include "SettingsManager.h"
14+
#include "find_usb_serial_port.h"
15+
#include <vector>
16+
#include <stdio.h>
17+
#include <stdlib.h>
18+
19+
#define CH341_VID 0x1A86
20+
#define CH341_PID 0x7523
21+
22+
void DetectArcticControllers()
23+
{
24+
std::vector<std::string *> ports = find_usb_serial_port(CH341_VID, CH341_PID);
25+
26+
for(unsigned int device = 0; device < ports.size(); device++)
27+
{
28+
ArcticController *controller = new ArcticController(*ports[device]);
29+
30+
if(controller->IsPresent())
31+
{
32+
RGBController_Arctic *rgb_controller = new RGBController_Arctic(controller);
33+
ResourceManager::get()->RegisterRGBController(rgb_controller);
34+
}
35+
else
36+
{
37+
delete controller;
38+
}
39+
40+
delete ports[device];
41+
}
42+
}
43+
44+
REGISTER_DETECTOR("Arctic RGB controller", DetectArcticControllers);
45+
/*---------------------------------------------------------------------------------------------------------*\
46+
| Entries for dynamic UDEV rules |
47+
| |
48+
| DUMMY_DEVICE_DETECTOR("Arctic RGB controller", DetectArcticControllers, 0x1a86, 0x7523 ) |
49+
\*---------------------------------------------------------------------------------------------------------*/
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*-----------------------------------------*\
2+
| RGBController_Arctic.h |
3+
| |
4+
| Generic RGB Interface for Arctic devices |
5+
| |
6+
| Armin Wolf (Wer-Wolf) 01/09/2023 |
7+
\*-----------------------------------------*/
8+
9+
#include "RGBController_Arctic.h"
10+
#include <math.h>
11+
12+
using namespace std::chrono_literals;
13+
14+
#define ARCTIC_NUM_CHANNELS 4
15+
#define ARCTIC_SLEEP_THRESHOLD 100ms
16+
#define ARCTIC_KEEPALIVE_PERIOD 500ms /* Device requires at least 1s */
17+
18+
/**------------------------------------------------------------------*\
19+
@name Arctic RGB Controller Devices
20+
@category LEDStrip
21+
@type Serial
22+
@save :x:
23+
@direct :white_check_mark:
24+
@effects :x:
25+
@detectors DetectArcticControllers
26+
@comment
27+
\*-------------------------------------------------------------------*/
28+
29+
RGBController_Arctic::RGBController_Arctic(ArcticController* controller_ptr)
30+
{
31+
controller = controller_ptr;
32+
33+
name = "Arctic RGB Controller";
34+
vendor = "Arctic";
35+
description = "Arctic 4-Channel RGB Controller";
36+
location = controller->GetLocation();
37+
type = DEVICE_TYPE_LEDSTRIP;
38+
39+
mode DirectMode;
40+
DirectMode.name = "Direct";
41+
DirectMode.value = 0;
42+
DirectMode.flags = MODE_FLAG_HAS_PER_LED_COLOR;
43+
DirectMode.color_mode = MODE_COLORS_PER_LED;
44+
45+
modes.push_back(DirectMode);
46+
47+
SetupZones();
48+
49+
keepalive_thread_run = true;
50+
keepalive_thread = std::thread(&RGBController_Arctic::KeepaliveThreadFunction, this);
51+
}
52+
53+
RGBController_Arctic::~RGBController_Arctic()
54+
{
55+
keepalive_thread_run = false;
56+
keepalive_thread.join();
57+
delete controller;
58+
}
59+
60+
void RGBController_Arctic::SetupZones()
61+
{
62+
for(int channel = 0; channel < ARCTIC_NUM_CHANNELS; channel++)
63+
{
64+
zone LedZone;
65+
LedZone.name = "LED Strip " + std::to_string(channel);
66+
LedZone.type = ZONE_TYPE_SINGLE;
67+
LedZone.leds_count = 1;
68+
LedZone.leds_min = 1;
69+
LedZone.leds_max = 1;
70+
LedZone.matrix_map = nullptr;
71+
72+
led Led;
73+
Led.name = LedZone.name + " LED";
74+
Led.value = channel;
75+
76+
zones.push_back(LedZone);
77+
leds.push_back(Led);
78+
}
79+
80+
SetupColors();
81+
}
82+
83+
void RGBController_Arctic::ResizeZone(int /* zone */, int /* new_size */)
84+
{
85+
/*---------------------------------------------------------*\
86+
| This device does not support resizing zones |
87+
\*---------------------------------------------------------*/
88+
}
89+
90+
void RGBController_Arctic::DeviceUpdateLEDs()
91+
{
92+
last_update_time = std::chrono::steady_clock::now();
93+
94+
controller->SetChannels(colors);
95+
}
96+
97+
void RGBController_Arctic::UpdateZoneLEDs(int /* zone */)
98+
{
99+
DeviceUpdateLEDs();
100+
}
101+
102+
void RGBController_Arctic::UpdateSingleLED(int /* led */)
103+
{
104+
DeviceUpdateLEDs();
105+
}
106+
107+
void RGBController_Arctic::DeviceUpdateMode()
108+
{
109+
/*---------------------------------------------------------*\
110+
| This device does not support mode updates |
111+
\*---------------------------------------------------------*/
112+
}
113+
114+
void RGBController_Arctic::KeepaliveThreadFunction()
115+
{
116+
std::chrono::nanoseconds sleep_time;
117+
118+
while(keepalive_thread_run.load())
119+
{
120+
sleep_time = ARCTIC_KEEPALIVE_PERIOD - (std::chrono::steady_clock::now() - last_update_time);
121+
if(sleep_time <= ARCTIC_SLEEP_THRESHOLD)
122+
{
123+
UpdateLEDs(); // Already protected thru a device update thread
124+
std::this_thread::sleep_for(ARCTIC_KEEPALIVE_PERIOD);
125+
}
126+
else
127+
{
128+
std::this_thread::sleep_for(sleep_time);
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)