時雨大好き! https://azusebox.moe azusebox Fri, 12 Aug 2022 01:43:04 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.0.7 https://azusebox.moe/wp-content/uploads/2017/05/cropped-C98RPkXUIAQewag-32x32.jpg 時雨大好き! https://azusebox.moe 32 32 143297362 任何QMK VIA键盘接入SignalRGB实现电脑端RGB控制 https://azusebox.moe/2022/08/12/%e4%bb%bb%e4%bd%95qmk-via%e9%94%ae%e7%9b%98%e6%8e%a5%e5%85%a5signalrgb%e5%ae%9e%e7%8e%b0%e7%94%b5%e8%84%91%e7%ab%afrgb%e6%8e%a7%e5%88%b6/ https://azusebox.moe/2022/08/12/%e4%bb%bb%e4%bd%95qmk-via%e9%94%ae%e7%9b%98%e6%8e%a5%e5%85%a5signalrgb%e5%ae%9e%e7%8e%b0%e7%94%b5%e8%84%91%e7%ab%afrgb%e6%8e%a7%e5%88%b6/#comments Thu, 11 Aug 2022 19:11:12 +0000 http://azusebox.moe/?p=13025 背景

客制化键盘玩法很多,有很多原厂高度的GMK配色键帽,但那都是不透光的。如果想要RGB,那可以整双皮奶键帽,全键RGB。更进一步,可以整亚克力外壳、透明轴、透明键帽…

前不久买一把我觉得是RGB终点的套件,方块男孩的RK680,Gasket,110颗 RGB灯,QMK VIA,南向轴座…

但这就是RGB终点了吗?!

看着一机箱的RGB灯,和这把无法和他们愉快同步的键盘,我幡然大悟!

灯效同步才是RGB的终点,我要给我这把键盘自己加灯效!

好在方块男孩键盘的开源程度很不错,轻松要到了工程文件,装好qmk工具就可以在wsl编译。但是因为这个键盘尼玛的reset键要拆了键盘才能按,一直懒得弄。开发RGB同步的念头就像咒语一样在脑海中徘徊了几个月。直到昨天不想上班休了一天假,在家才坐下来仔细研究了一下RGB同步能否实现。

先上下最后同步的效果:







QMK VIA

qmk是开源键盘驱动Quantum Mechanical Keyboard的缩写,支持非常多的客制化键盘,支持自定义键位、自定义层、自带RGB灯效等功能,如果在2022年你的键盘还没有用上qmk,那么你可以考虑换一把了。
via是什么就比较难懂了,直观来看,只要支持via的键盘,就可以通过via的电脑程序或者webapp获得实时修改键位、调整RGB光效的能力,不用再像QMK每次改键都要编译一个新的固件出来刷机了。从底层来看,via是附加在qmk上的一层自定义的协议,通过UsbHID和电脑通信,定义的协议中实现了一些功能就有修改键位、修改RGB光效。

\

开启VIA需要在rule.mk中设置VIA_ENABLED=yes,当VIA开启的时候,RAWHID会自动开启,可以不需要额外设置RAW_ENABLE=yes。编译QMK固件的过程不再在这里赘述,需要确认编译好的固件可以正常使用VIA软件,这代表电脑-usbhid-via这条路就已经通了。

SignalRGB

SignalRGB是现在一款统一RGB软件,可以统一控制华硕Aurora、技嘉fusion、海盗船、雷蛇等很多厂商的RGB,他采取了反向工程的方法,破解出厂商RGB通信的协议,然后再用自己实现的服务端和RGB设备通信,起到控制RGB灯效的作用。在统一RGB领域中还有一款是OpenRGB,全开源软件,但是我觉得UI比较简陋,还是更喜欢用SignalRGB。

华硕、技嘉、海盗船这些厂商对于RGB通信协议都是支支吾吾的,从来没有一份公开的文档或者SDK,现在开源的SDK都是无数玩家开发者贡献出来的反向工程代码,OpenRGB就是这些代码的集大成者,SignalRGB这个软件也极大程度受益于OpenRGB提供的开源协议。诸如内存灯光,显卡灯光,可能是厂家通过向内存/PCIE设备一个指定的地址,按照指定的格式写RGB信息实现的控制方法,都是大佬们通过反向工程抓包解析出来的。有了OpenRGB和SignalRGB这样的平台铺路,我们一般人想写个插件支持下新的设备,或者添加一个自定义的光效,就变得非常简单了。

https://signalrgb.com/devices/
这是SignalRGB目前支持的设备列表,主流厂商并不是尽数支持的,可以进去看看你的设备支持不支持。很巧的是,我用的华硕主板+海盗船内存+技嘉显卡,SignalRGB都是支持的,所以它就成了我得最佳选择,赶紧抛弃掉这三个厂商的垃圾RGB控制软件。

我的设备列表,技嘉显卡送去修了所以这里没有

它支持的RGB效果很多,你也可以自己开发效果

每一个RGB效果都有速度、颜色等参数可以自定义

RGB控制采取类似桌面布局的方式,可以把设备放到对应的位置,大小方向都可以调整

对主板的每个argb插针都可以单独控制

总之这是一款特别棒的RGB控制软件,经过一段时间使用之后也对基于它二次开发有了兴趣。哦对了,他们自己也卖RGB灯条,牌子叫whirlwindfx,小贵,并且只寄美国和加拿大,淘宝没有。

我的键盘

先说下我使用的这把键盘

这把键盘是方块男孩的FK680Pro套件,用的V2版本的PCB,正面有70颗LED灯,背面有40颗LED灯,使用QMK固件支持VIA,单模USB接口。
这里用的键帽是AKKO透明键帽,轴是幻想轴。
这是把不错的入门QMK的键盘,套件价格只要不到三百元,键盘手感不错,RGB灯很多,硬件软件全开源,群里有图纸和代码,工程文件可以找淘宝客服要,很适合二次开发RGB灯效。

基于SignalRGB的二次开发

SignalRGB的很多设备是通过Javascript插件来支持的,对于尚不支持的设备,开发者鼓励自己开发Javascript脚本支持。
官方对于如何从头开始制作一个脚本有详细的教程,特别解释了如何去抓USB HID包,因为大部分RGB都是通过HID来控制的。

从0开始反向工程一个USBHID协议还是一个挺难的工程,我配置了半天wireshark,都没看到一个有效的hid包。不过好在我们今天做的QMK VIA键盘不需要我们反向工程。QMK VIA代码是开源的,并且SignalRGB的Javascript脚本也是开源的,只要参考官方的脚本和代码,我们就可以将自己的QMK键盘也接入SignalRGB的控制。

细读修改QMK VIA的RGB Matrix代码与USBHID代码,加入SignalRGB支持

前言,SignalRGB改造的协议、handler都是SignalRGB的开发者写好的,只是SignalRGB尚未有详细的文档教人如何去自己改造QMK固件,官方只发布了三个QMK键盘的支持:https://signalrgb.developerhub.io/qmk/supported-keyboards

可以预见官方肯定会加强QMK的支持,但目前我只能根据这已经放出的三把键盘,改造一下我得QMK固件,自己编译一个支持SignalRGB的固件,同时在写一个电脑端调用脚本。好在我有上面链接里的三把键盘可以参考。不幸的是官方是放出了编译好的固件,所以qmk固件中的修改,还要去官方的qmk仓库中找:signalrgb/qmk_firmware

RGB

QMK控制RGB等效有两种接口,一个叫rgblight,FLAG是RGBLIGHT_ENABLE,这个比较旧了,效果也很简单;另一个是rgbmatrix,FLAG是RGB_MATRIX_ENABLE,如其名字是矩阵化的RGB等效,这两个的区别有点像12v4pin的RGB和5v3pin的ARGB的效果区别,matrix的灯效更精细,可以per键控制。FK680键盘默认使用rgbmatrix控制rgb。

rgb_matrix中需要使用的函数是rgb_matrix_set_color,可以设置一个灯泡的rgb值,灯泡从0开始顺序计数的,一般来说一行一行递增计数。rgb_matrix的动画效果存放在animation文件中,动画需要先在rgb_matrix_effects.inc中定义,然后在定义的头文件里实现RGB效果。

rgb_matrix支持使用RGB灯条控制器,FK680pro自带的控制器是WS2812。

为了实现SignalRGB接入,需要在animation创建一个自定义的动画:SIGNALRGB

// animations/signalrgb_anim.h
RGB_MATRIX_EFFECT(SIGNALRGB)
#ifdef RGB_MATRIX_CUSTOM_EFFECT_IMPLS

bool SIGNALRGB(effect_params_t* params)
{
    RGB_MATRIX_USE_LIMITS(led_min, led_max);

    return rgb_matrix_check_finished_leds(led_max);
}

#endif // RGB_MATRIX_CUSTOM_EFFECT_IMPLS

然后在头文件中加入SIGNALRGB的动画定义

// animations/rgb_matrix_effects.inc
// Add your new core rgb matrix effect here, order determines enum order
#include "signalrgb_anim.h"

这个动画模式的目的是给后面从电脑控制RGB预留一个空白的RGB模式,这样电脑传过来的RGB信号不会和固件发出的RGB冲突。

VIA

VIA实现在quantum/via.c中,VIA的核心就是在 raw_hid_receive 函数中实现了一系列的USBHID命令,包括实时修改键位、设置RGB灯等等。

VIA的USBHID协议包含的命令可以看via.h头文件。从0x010x13是via原生定义的指令,0x21开始则是signalRGB自己新增加的命令,SignalRGB的电脑端就是通过这些命令和VIA键盘通信的。包括获得qmk版本、via版本、signalrgb指令版本、开启关闭signalrgb,最重要的id_signalrgb_stream_leds才是设置LED颜色,控制RGB的函数。

enum via_command_id {
    id_get_protocol_version                 = 0x01, // always 0x01
    id_get_keyboard_value                   = 0x02,
    id_set_keyboard_value                   = 0x03,
    id_dynamic_keymap_get_keycode           = 0x04,
    id_dynamic_keymap_set_keycode           = 0x05,
    id_dynamic_keymap_reset                 = 0x06,
    id_lighting_set_value                   = 0x07,
    id_lighting_get_value                   = 0x08,
    id_lighting_save                        = 0x09,
    id_eeprom_reset                         = 0x0A,
    id_bootloader_jump                      = 0x0B,
    id_dynamic_keymap_macro_get_count       = 0x0C,
    id_dynamic_keymap_macro_get_buffer_size = 0x0D,
    id_dynamic_keymap_macro_get_buffer      = 0x0E,
    id_dynamic_keymap_macro_set_buffer      = 0x0F,
    id_dynamic_keymap_macro_reset           = 0x10,
    id_dynamic_keymap_get_layer_count       = 0x11,
    id_dynamic_keymap_get_buffer            = 0x12,
    id_dynamic_keymap_set_buffer            = 0x13,
    id_unhandled                            = 0xFF,
    id_signalrgb_qmk_version                = 0x21,
    id_signalrgb_protocol_version           = 0x22,
    id_signalrgb_unique_identifier          = 0x23,
    id_signalrgb_stream_leds                = 0x24,
    id_signalrgb_effect_enable              = 0x25,
    id_signalrgb_effect_disable             = 0x26,
    id_signalrgb_get_total_leds             = 0x27,
    id_signalrgb_get_firmware_type          = 0x28,
};

VIA的raw_hid_receive函数就是一个很简单的switch,传入的data是1字节uint8_t数组,第一个1字节data[0]是指令,之后的都是数据段,可以在各个指令里自己定义。这段SWITCH最后就是signalrgb相关的handler,signalrgb的开发者已经把他们实现好了,我们只需要拷贝一份。仓库:signalrgb/qmk_firmware

// VIA handles received HID messages first, and will route to
// raw_hid_receive_kb() for command IDs that are not handled here.
// This gives the keyboard code level the ability to handle the command
// specifically.
//
// raw_hid_send() is called at the end, with the same buffer, which was
// possibly modified with returned values.
void raw_hid_receive(uint8_t *data, uint8_t length) {
    uint8_t *command_id   = &(data[0]);
    uint8_t *command_data = &(data[1]);
    switch (*command_id) {
        case id_get_protocol_version: {
            command_data[0] = VIA_PROTOCOL_VERSION >> 8;
            command_data[1] = VIA_PROTOCOL_VERSION & 0xFF;
            break;
        }
        case id_get_keyboard_value: {
            switch (command_data[0]) {
                case id_uptime: {
                    uint32_t value  = timer_read32();
                    command_data[1] = (value >> 24) & 0xFF;
                    command_data[2] = (value >> 16) & 0xFF;
                    command_data[3] = (value >> 8) & 0xFF;
                    command_data[4] = value & 0xFF;
                    break;
                }
                case id_layout_options: {
                    uint32_t value  = via_get_layout_options();
                    command_data[1] = (value >> 24) & 0xFF;
                    command_data[2] = (value >> 16) & 0xFF;
                    command_data[3] = (value >> 8) & 0xFF;
                    command_data[4] = value & 0xFF;
                    break;
                }
                case id_switch_matrix_state: {
#if ((MATRIX_COLS / 8 + 1) * MATRIX_ROWS <= 28)
                    uint8_t i = 1;
                    for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
                        matrix_row_t value = matrix_get_row(row);
#    if (MATRIX_COLS > 24)
                        command_data[i++] = (value >> 24) & 0xFF;
#    endif
#    if (MATRIX_COLS > 16)
                        command_data[i++] = (value >> 16) & 0xFF;
#    endif
#    if (MATRIX_COLS > 8)
                        command_data[i++] = (value >> 8) & 0xFF;
#    endif
                        command_data[i++] = value & 0xFF;
                    }
#endif
                    break;
                }
                default: {
                    raw_hid_receive_kb(data, length);
                    break;
                }
            }
            break;
        }
        case id_set_keyboard_value: {
            switch (command_data[0]) {
                case id_layout_options: {
                    uint32_t value = ((uint32_t)command_data[1] << 24) | ((uint32_t)command_data[2] << 16) | ((uint32_t)command_data[3] << 8) | (uint32_t)command_data[4];
                    via_set_layout_options(value);
                    break;
                }
                default: {
                    raw_hid_receive_kb(data, length);
                    break;
                }
            }
            break;
        }
        case id_dynamic_keymap_get_keycode: {
            uint16_t keycode = dynamic_keymap_get_keycode(command_data[0], command_data[1], command_data[2]);
            command_data[3]  = keycode >> 8;
            command_data[4]  = keycode & 0xFF;
            break;
        }
        case id_dynamic_keymap_set_keycode: {
            dynamic_keymap_set_keycode(command_data[0], command_data[1], command_data[2], (command_data[3] << 8) | command_data[4]);
            break;
        }
        case id_dynamic_keymap_reset: {
            dynamic_keymap_reset();
            break;
        }
        case id_lighting_set_value: {
#if defined(VIA_QMK_BACKLIGHT_ENABLE)
            via_qmk_backlight_set_value(command_data);
#endif
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
            via_qmk_rgblight_set_value(command_data);
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
            via_qmk_rgb_matrix_set_value(command_data);
#endif
#if defined(VIA_CUSTOM_LIGHTING_ENABLE)
            raw_hid_receive_kb(data, length);
#endif
#if !defined(VIA_QMK_BACKLIGHT_ENABLE) && !defined(VIA_QMK_RGBLIGHT_ENABLE) && !defined(VIA_CUSTOM_LIGHTING_ENABLE) && !defined(VIA_QMK_RGB_MATRIX_ENABLE)
            // Return the unhandled state
            *command_id = id_unhandled;
#endif
            break;
        }
        case id_lighting_get_value: {
#if defined(VIA_QMK_BACKLIGHT_ENABLE)
            via_qmk_backlight_get_value(command_data);
#endif
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
            via_qmk_rgblight_get_value(command_data);
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
            via_qmk_rgb_matrix_get_value(command_data);
#endif
#if defined(VIA_CUSTOM_LIGHTING_ENABLE)
            raw_hid_receive_kb(data, length);
#endif
#if !defined(VIA_QMK_BACKLIGHT_ENABLE) && !defined(VIA_QMK_RGBLIGHT_ENABLE) && !defined(VIA_CUSTOM_LIGHTING_ENABLE)  && !defined(VIA_QMK_RGB_MATRIX_ENABLE)
            // Return the unhandled state
            *command_id = id_unhandled;
#endif
            break;
        }
        case id_lighting_save: {
#if defined(VIA_QMK_BACKLIGHT_ENABLE)
            eeconfig_update_backlight_current();
#endif
#if defined(VIA_QMK_RGBLIGHT_ENABLE)
            eeconfig_update_rgblight_current();
#endif
#if defined(VIA_QMK_RGB_MATRIX_ENABLE)
            eeconfig_update_rgb_matrix();
#endif
#if defined(VIA_CUSTOM_LIGHTING_ENABLE)
            raw_hid_receive_kb(data, length);
#endif
#if !defined(VIA_QMK_BACKLIGHT_ENABLE) && !defined(VIA_QMK_RGBLIGHT_ENABLE) && !defined(VIA_CUSTOM_LIGHTING_ENABLE)  && !defined(VIA_QMK_RGB_MATRIX_ENABLE)
            // Return the unhandled state
            *command_id = id_unhandled;
#endif
            break;
        }
#ifdef VIA_EEPROM_ALLOW_RESET
        case id_eeprom_reset: {
            via_eeprom_set_valid(false);
            eeconfig_init_via();
            break;
        }
#endif
        case id_dynamic_keymap_macro_get_count: {
            command_data[0] = dynamic_keymap_macro_get_count();
            break;
        }
        case id_dynamic_keymap_macro_get_buffer_size: {
            uint16_t size   = dynamic_keymap_macro_get_buffer_size();
            command_data[0] = size >> 8;
            command_data[1] = size & 0xFF;
            break;
        }
        case id_dynamic_keymap_macro_get_buffer: {
            uint16_t offset = (command_data[0] << 8) | command_data[1];
            uint16_t size   = command_data[2]; // size <= 28
            dynamic_keymap_macro_get_buffer(offset, size, &command_data[3]);
            break;
        }
        case id_dynamic_keymap_macro_set_buffer: {
            uint16_t offset = (command_data[0] << 8) | command_data[1];
            uint16_t size   = command_data[2]; // size <= 28
            dynamic_keymap_macro_set_buffer(offset, size, &command_data[3]);
            break;
        }
        case id_dynamic_keymap_macro_reset: {
            dynamic_keymap_macro_reset();
            break;
        }
        case id_dynamic_keymap_get_layer_count: {
            command_data[0] = dynamic_keymap_get_layer_count();
            break;
        }
        case id_dynamic_keymap_get_buffer: {
            uint16_t offset = (command_data[0] << 8) | command_data[1];
            uint16_t size   = command_data[2]; // size <= 28
            dynamic_keymap_get_buffer(offset, size, &command_data[3]);
            break;
        }
        case id_dynamic_keymap_set_buffer: {
            uint16_t offset = (command_data[0] << 8) | command_data[1];
            uint16_t size   = command_data[2]; // size <= 28
            dynamic_keymap_set_buffer(offset, size, &command_data[3]);
            break;
        }
        case id_signalrgb_qmk_version:

        get_qmk_version();

        break;
        case id_signalrgb_protocol_version:

        get_signalrgb_protocol_version();

        break;
        case id_signalrgb_unique_identifier:

        get_unique_identifier();

        break;
        case id_signalrgb_stream_leds:

        led_streaming(data);

        break;

        case id_signalrgb_effect_enable:

        signalrgb_mode_enable();

        break;

        case id_signalrgb_effect_disable:

        signalrgb_mode_disable();

        break;

        case id_signalrgb_get_total_leds:

        get_total_leds();

        break;

        case id_signalrgb_get_firmware_type:

        get_firmware_type();

        break;

        default: {
            // The command ID is not known
            // Return the unhandled state
            *command_id = id_unhandled;
            break;
        }
    }

SignalRGB实现好了的handler。我们也可以直接拷贝,其中最重要的ledstream指令,传入的是一个led index + led num + rgb list的结构,data[1]代表设置的第一个灯泡index,data[2]是接下来数据里包含的灯泡数量,data[2]之后是一组组长度为24(3字节)的 (R,G,B)数据。

 uint8_t packet[32];

 void get_qmk_version(void) //Grab the QMK Version
{
        packet[0] = id_signalrgb_qmk_version;
        packet[1] = QMK_VERSION_BYTE_1;
        packet[2] = QMK_VERSION_BYTE_2;
        packet[3] = QMK_VERSION_BYTE_3;

        raw_hid_send(packet, 32);
}

void get_signalrgb_protocol_version(void) //Grab what version of the SignalRGB protocol a keyboard is running
{
        packet[0] = id_signalrgb_protocol_version;
        packet[1] = PROTOCOL_VERSION_BYTE_1;
        packet[2] = PROTOCOL_VERSION_BYTE_2;
        packet[3] = PROTOCOL_VERSION_BYTE_3;

        raw_hid_send(packet, 32);
}

void get_unique_identifier(void) //Grab the unique identifier for each specific model of keyboard.
{
        packet[0] = id_signalrgb_unique_identifier;
        packet[1] = DEVICE_UNIQUE_IDENTIFIER_BYTE_1;
        packet[2] = DEVICE_UNIQUE_IDENTIFIER_BYTE_2;
        packet[3] = DEVICE_UNIQUE_IDENTIFIER_BYTE_3;

        raw_hid_send(packet, 32);
}

void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
    uint8_t index = data[1];
    uint8_t numberofleds = data[2]; 

    if(numberofleds >= 10)
    {
        packet[1] = DEVICE_ERROR_LEDS;
        raw_hid_send(packet,32);
        return; 
    } 

    for (uint8_t i = 0; i < numberofleds; i++)
    {
      uint8_t offset = (i * 3) + 3;
      uint8_t  r = data[offset];
      uint8_t  g = data[offset + 1];
      uint8_t  b = data[offset + 2];

      rgb_matrix_set_color(index + i, r, g, b);
     }
}

void signalrgb_mode_enable(void)
{
    rgb_matrix_mode_noeeprom(RGB_MATRIX_SIGNALRGB); //Set RGB Matrix to SignalRGB Compatible Mode
}

void signalrgb_mode_disable(void)
{
    rgb_matrix_reload_from_eeprom(); //Reloading last effect from eeprom
}

void get_total_leds(void)//Grab total number of leds that a board has.
{
    packet[0] = id_signalrgb_get_total_leds;
    packet[1] = DRIVER_LED_TOTAL;

    raw_hid_send(packet, 32);
}

void get_firmware_type(void) //Grab which fork of qmk a board is running.
{
    packet[0] = id_signalrgb_get_firmware_type;
    packet[1] = FIRMWARE_TYPE_BYTE;

    raw_hid_send(packet, 32);
}

在这里补充下FK680Pro键盘的LED INDEX,在SignalRGB中定义RGB矩阵时会用到:

细读修改SignalRGB的设备接入脚本

这里我们先参考一下SignalRGB官方放出的KDBFans KDB67的QMK VIA支持脚本

首先是头部,定义了一些脚本的基本信息,这些是提供给SignalRGB显示用的。

export function Name() { return "KBDFans KBD67"; }
export function VendorId() { return 0x4b42; }
export function ProductId() { return 0x1225; }
export function Publisher() { return "WhirlwindFX"; }
export function Size() { return [15, 5]; }
export function DefaultPosition(){return [10, 100]; }
export function DefaultScale(){return 8.0}
export function ControllableParameters() {
    return [
        {"property":"shutdownColor", "group":"lighting", "label":"Shutdown Color", "min":"0", "max":"360", "type":"color", "default":"009bde"},
        {"property":"LightingMode", "group":"lighting", "label":"Lighting Mode", "type":"combobox", "values":["Canvas", "Forced"], "default":"Canvas"},
        {"property":"forcedColor", "group":"lighting", "label":"Forced Color", "min":"0", "max":"360", "type":"color", "default":"009bde"},
    ];
}

接下来是正戏,在vKeys中定义的键位(应该说LED顺序),在vKeyNames中定义的每一个LED名字,在vKeyPositions里定义的每一个LED的抽象位置。

vKeys中的可以值可以对LED排序再做一次映射,比较绕,我就直接保持了从0开始递增到最后一个LED结束的规则,我有110颗LED,就是[0, … 109]

vKeyNames只是debug用的名称显示,自己取个名字就行,但是注意这三个数组长度必须一样。

vKeyPosition是每一个LED在SignalRGB的Layout中的抽象位置,键盘被划成5×15的区域(这个在第一段代码中第一),比如最左上的ESC位置就是[0,0],左下的Ctrl就是[0,4],这里比较坑的是:这里的顺序,ESC所在的LED灯的INDEX值是0。

const vKeys = 
[
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
    44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
    58, 59, 60, 61, 62, 63, 64, 65, 66
];
const vKeyNames = 
[
    "Esc","1","2", "3", "4", "5",  "6", "7", "8", "9", "0",  "-", "+",  "Backspace","Home", //15
    "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\","Page Up",     //15
    "CapsLock", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'","Enter",  "Page Down",    //14
    "Left Shift","Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Right Shift",    "Up Arrow",   "End",   //14
    "Left Ctrl", "Left Win", "Left Alt", "Space", "Right Alt", "Fn", "Left Arrow",  "Down Arrow", "Right Arrow", //9

];

const vKeyPositions = 
[
[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0], [12,0], [13,0], [14,0], //15
[0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1], [12,1], [13,1], [14,1], //15 
[0,2],[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2],         [13,2], [14,2], //14                    
[0,3],[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3],         [13,3], [14,3],  //14
[0,4],[1,4],[2,4],                  [6,4],                  [10,4],[11,4], [12,4], [13,4], [14,4]  //9
];

但实际上在我的键盘上,Left Ctrl的LED INDEX才是0,所以这里要做顺序调整,也是我花了一些时间才搞出来的。之外我得空格下有3颗LED,所以空格中间也需要再加两颗LED。最后这是67键盘的键位,我是68键盘,最下层再多一个键。最最后我还有背面40颗灯珠,还得给后买后面的灯珠设置逻辑位置。这里直接公布答案了,照抄就行:

const vKeys = 
[
    0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11,                             // 12
    12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,                     // 14
    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,                     // 14
    40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,                 // 15
    55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,                 // 15
    70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 // 40
];
const vKeyNames = 
[
    "Left Ctrl", "Left Win", "Left Alt", "Space0", "Space1", "Space2", "Right Ctrl", "Fn", "Right Alt", "Left Arrow",  "Down Arrow", "Right Arrow", //12
    "Left Shift","Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Right Shift",    "Up Arrow",   "End",   //14
    "CapsLock", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'","Enter",  "Page Down",    //14
    "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\","Page Up",     //15
    "Esc","1","2", "3", "4", "5",  "6", "7", "8", "9", "0",  "-", "+",  "Backspace","Home", //15
    "bk0", "bk1", "bk2", "bk3", "bk4", "bk5", "bk6", "bk7", "bk8", "bk9", "bk10", "bk11", "bk12", "bk13", "bk14", "bk15", "bk16", "bk17", "bk18", "bk19", "bk20", 
    "bk21", "bk22", "bk23", "bk24", "bk25", "bk26", "bk27", "bk28", "bk29", "bk30", "bk31", "bk32", "bk33", "bk34", "bk35", "bk36", "bk37", "bk38", "bk39"
];

const vKeyPositions = 
[
    [0,4],[1,4],[2,4],      [4,4],      [6,4],      [8,4],[9,4],[10,4],[11,4], [12,4], [13,4], [14,4], //12
    [0,3],[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3],         [13,3], [14,3], //14
    [0,2],[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2],         [13,2], [14,2], //14                    
    [0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1], [12,1], [13,1], [14,1], //15 
    [0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0], [12,0], [13,0], [14,0], //15
    [14,0],[14,1],[14,2],[14,3],[14,4],
    [14,4],[13,4],[12,4],[11,4],[10,4],[9,4],[8,4],[7,4],[6,4],[5,4],[4,4],[3,4],[2,4],[1,4],[0,4],
    [0,4],[0,3],[0,2],[0,1],[0,0],
    [0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0],[12,0],[13,0],[14,0],`-`
];

后面是从SignalRGB获取每一个逻辑位置RGB值得代码,和实际构造数据包,发送USBHID的代码,如果使用SignalRGB开发者设计好的协议的话这段不需要修改,当然你可以修改协议实现自己的RGB或者别的功能。

export function LedNames() 
{
    return vKeyNames;
}

export function LedPositions() 
{
    return vKeyPositions;
}

export function Initialize() 
{
    ClearReadBuffer();
    versionVIA();
    versionQMK();
    versionSignalRGBProtocol();
    uniqueIdentifier();
    effectEnable();
}

export function Render() 
{
    sendColors();
}

export function Shutdown() 
{
    effectDisable();
}

function ClearReadBuffer(timeout = 10){
    let count = 0;
    let readCounts = [];
    device.flush();

    while(device.getLastReadSize() > 0){
        device.read([0x00], 32, timeout);
        count++;
        readCounts.push(device.getLastReadSize());
    }
    //device.log(`Read Count {count}:{readCounts} Bytes`)
}

function versionVIA()
{
    var packet = [];
    packet[0] = 0x00;
    packet[1] = 0x01;

    device.write(packet, 32);
    packet = device.read(packet,32);
    let via_version = packet[3];
    device.log("Via Protocol Version: " + via_version);
}

function versionQMK() //Check the version of QMK Firmware that the keyboard is running
{
    var packet = [];
    packet[0] = 0x00;
    packet[1] = 0x21;

    device.write(packet, 32);
    packet = device.read(packet,32);
    let QMKVersionByte1 = packet[2];
    let QMKVersionByte2 = packet[3];
    let QMKVersionByte3 = packet[4];
    device.log("QMK Version: " + QMKVersionByte1 + "." + QMKVersionByte2 + "." + QMKVersionByte3);
    device.pause(30);
}

function versionSignalRGBProtocol() //Grab the version of the SignalRGB Protocol the keyboard is running
{
    var packet = [];
    packet[0] = 0x00;
    packet[1] = 0x22;

    device.write(packet, 32);
    packet = device.read(packet,32);
    let ProtocolVersionByte1 = packet[2];
    let ProtocolVersionByte2 = packet[3];
    let ProtocolVersionByte3 = packet[4];
    device.log("SignalRGB Protocol Version: " + ProtocolVersionByte1 + "." + ProtocolVersionByte2 + "." + ProtocolVersionByte3);
    device.pause(30);
}

function uniqueIdentifier() //Grab the unique identifier for this keyboard model
{
    var packet = [];
    packet[0] = 0x00;
    packet[1] = 0x23;

    device.write(packet, 32);
    packet = device.read(packet,32);
    let UniqueIdentifierByte1 = packet[2];
    let UniqueIdentifierByte2 = packet[3];
    let UniqueIdentifierByte3 = packet[4];
    device.log("Unique Device Identifier: " + UniqueIdentifierByte1 + UniqueIdentifierByte2 + UniqueIdentifierByte3);
    device.pause(30);
}

function effectEnable() //Enable the SignalRGB Effect Mode
{
    let packet = [];
    packet[0] = 0x00;
    packet[1] = 0x25;

    device.write(packet,32);
    device.pause(30);
}

function effectDisable() //Revert to Hardware Mode
{
    let packet = [];
    packet[0] = 0x00;
    packet[1] = 0x26;

    device.write(packet,32);  
}

function grabColors(shutdown = false) 
{
    let rgbdata = [];

    for(let iIdx = 0; iIdx < vKeys.length; iIdx++)
    {
        let iPxX = vKeyPositions[iIdx][0];
        let iPxY = vKeyPositions[iIdx][1];
        let color;

        if(shutdown)
        {
            color = hexToRgb(shutdownColor);
        }
        else if (LightingMode === "Forced")
        {
            color = hexToRgb(forcedColor);
        }
        else
        {
            color = device.color(iPxX, iPxY);
        }

        let iLedIdx = vKeys[iIdx] * 3;
        rgbdata[iLedIdx] = color[0];
        rgbdata[iLedIdx+1] = color[1];
        rgbdata[iLedIdx+2] = color[2];
    }

    return rgbdata;
}

function sendColors()
{
    let rgbdata = grabColors();

    for(var index = 0; index < 8; index++) //This will need rounded up to closest value for your board.
    {
    let packet = [];
    let offset = index * 9;
    packet[0] = 0x00;
    packet[1] = 0x24;
    packet[2] = offset;
    packet[3] = 0x09;
    packet = packet.concat(rgbdata.splice(0, 27));
    device.write(packet, 33);

    }

}

function hexToRgb(hex) 
{
    let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    let colors = [];
    colors[0] = parseInt(result[1], 16);
    colors[1] = parseInt(result[2], 16);
    colors[2] = parseInt(result[3], 16);

    return colors;
}

export function Validate(endpoint) 
{
    return endpoint.interface === 1;
}

export function Image() 
{
    return "";
}

注意脚本中打印的device.log需要在SignalRGB的设备管理里头看:

开发好的自定义脚本可以加上Image,放到C:\%USER%\Documents\WhirlwindFX\Plugins\


到此,只要刷上新的固件,在SignalRGB中导入你的脚本,就可以愉快的用SignalRGB控制你的键盘了


扩展:添加模式切换,大小写,饱和度亮度调整等

FK680键盘原来有个CapsLock灯的效果,这个是在qmk_firmware\keyboards\FK680ProV2\keymaps\via\keymap.c中由键盘开发者实现的,代码逻辑如下:

这段代码同时实现了两个功能:如果上/下键盘RGB等效关闭则关灯;如果大写锁定打开,则打开index=26灯。从键盘LED INDEX可以看出26就是大小写键。

void rgb_matrix_indicators_advanced_user(uint8_t led_min, uint8_t led_max) {

    if (user_config.top_rgb_change)
    {
        for (size_t i = 0; i < 70; i++)
        {
            RGB_MATRIX_INDICATOR_SET_COLOR(i, 0, 0, 0);
        }
    }

    if (host_keyboard_led_state().caps_lock) {
        RGB_MATRIX_INDICATOR_SET_COLOR(26, 0, 255, 255); // assuming caps lock is at led #5
    }

    if (user_config.bottom_rgb_change)
    {
        for (size_t i = 70; i < 110; i++)
        {
            RGB_MATRIX_INDICATOR_SET_COLOR(i, 0, 0, 0);
        }
    }
}

在支持SignalRGB之后,SignalRGB会覆盖大小写灯,还原原来的功能只需要加一行continue:

void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
    uint8_t index = data[1];
    uint8_t numberofleds = data[2];

    if(numberofleds >= 10)
    {
        packet[1] = DEVICE_ERROR_LEDS;
        raw_hid_send(packet,32);
        return;
    }

    for (uint8_t i = 0; i < numberofleds; i++)
    {
      uint8_t offset = (i * 3) + 3;
      uint8_t  r = data[offset];
      uint8_t  g = data[offset + 1];
      uint8_t  b = data[offset + 2];
      if (index + i == 26 && host_keyboard_led_state().caps_lock) continue;

     rgb_matrix_set_color(index + i, r, g, b);
     }
}

饱和度和亮度调整是在计算RGB是使用HSV格式,修改 S(atuation) 和 V(alue) 实现的,同时 H(ue) 也是可以修改的,不过 Hue对于有RGB颜色循环的等效无效。使用SignalRGB之后,键盘直接显示电脑端传过来的RGB值,这样很直接。但是我们也可以加上原来的饱和度S,亮度S调节。通过将RGB值先用rgb_to_hsv转换成HSV值,在对S和V进行微调,然后再用hsv_to_rgb转换回RGB格式,调用rgb_matrix_set_color显示。这么做是可行的,可以通过键盘对电脑同步RGB灯效再做加强/减弱饱和度/亮度的调整。但是我也遇到了问题,rgb->hsv->rgb这样的转换造成了一些色彩的丢失,特别表现在亮度上,原来变化自然的明暗变得非常生硬,几乎没有了亮度变化,不知道是转换中不可避免的损失,还是我哪里写错了。

void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
    uint8_t index = data[1];
    uint8_t numberofleds = data[2];

    if(numberofleds >= 10)
    {
        packet[1] = DEVICE_ERROR_LEDS;
        raw_hid_send(packet,32);
        return;
    }

    for (uint8_t i = 0; i < numberofleds; i++)
    {
      uint8_t offset = (i * 3) + 3;
      uint8_t  r = data[offset];
      uint8_t  g = data[offset + 1];
      uint8_t  b = data[offset + 2];
      if (index + i == 26 && host_keyboard_led_state().caps_lock) continue;
      if (rgb_matrix_get_mode() != RGB_MATRIX_SIGNALRGB) {
        if (user_config.top_rgb_signal == true && index + i < 70) {
            rgb_matrix_set_color(index + i, r, g, b);
            continue;
        }
        else if (user_config.bottom_rgb_signal == true && index + i >= 70) {
            rgb_matrix_set_color(index + i, r, g, b);
            continue;
        }
        else continue;
      };

    //   rgb_matrix_set_color(index + i, r, g, b);
      RGB rgb;
      rgb.r = r;
      rgb.g = g;
      rgb.b = b;
      HSV hsv = rgb_to_hsv(rgb);

      hsv.s = hsv.s + 128 - rgb_matrix_config.hsv.s;
      hsv.s = hsv.s > 255 ? 255 : hsv.s;
      hsv.s = hsv.s < 0 ? 0 : hsv.s;

      hsv.v = hsv.v + 128 - rgb_matrix_config.hsv.v;
      hsv.v = hsv.v > 255 ? 255 : hsv.v;
      hsv.v = hsv.v < 0 ? 0 : hsv.v;

     rgb = hsv_to_rgb(hsv);

      rgb_matrix_set_color(index + i, rgb.r, rgb.g, rgb.b);
     }
}

这里用到的rgb_to_hsv不是qmk代码中自带的,是我从stackoverflow随便复制的一个函数:

HSV rgb_to_hsv(RGB rgb)
{
    HSV hsv;
    uint8_t rgbMin, rgbMax;

    rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b);
    rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b);

    hsv.v = rgbMax;
    if (hsv.v == 0)
    {
        hsv.h = 0;
        hsv.s = 0;
        return hsv;
    }

    long delta = rgbMax - rgbMin;
    hsv.s = 255 * delta / hsv.v;
    if (hsv.s == 0)
    {
        hsv.h = 0;
        return hsv;
    }

    if (rgbMax == rgb.r)
        hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin);
    else if (rgbMax == rgb.g)
        hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin);
    else
        hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin);

    return hsv;
}

扩展:自定义动画

最后一个bonus,我想实现的上层字母区LED和下层背面LED实现不同的动画:

在SignalRGB的led_streaming函数中添加如下开关:
user_config.top_rgb_signalTrue时上层RGB(前70个灯)一定输出;
user_config.bottom_rgb_signalTrue时下层RGB(后40个灯)一定输出;

void led_streaming(uint8_t *data) //Stream data from HID Packets to Keyboard.
{
    uint8_t index = data[1];
    uint8_t numberofleds = data[2];

    if(numberofleds >= 10)
    {
        packet[1] = DEVICE_ERROR_LEDS;
        raw_hid_send(packet,32);
        return;
    }

    for (uint8_t i = 0; i < numberofleds; i++)
    {
      uint8_t offset = (i * 3) + 3;
      uint8_t  r = data[offset];
      uint8_t  g = data[offset + 1];
      uint8_t  b = data[offset + 2];
      if (index + i == 26 && host_keyboard_led_state().caps_lock) continue;
      if (rgb_matrix_get_mode() != RGB_MATRIX_SIGNALRGB) {
        if (user_config.top_rgb_signal == true && index + i < 70) {
            rgb_matrix_set_color(index + i, r, g, b);
            continue;
        }
        else if (user_config.bottom_rgb_signal == true && index + i >= 70) {
            rgb_matrix_set_color(index + i, r, g, b);
            continue;
        }
        else continue;
      };

      rgb_matrix_set_color(index + i, r, g, b);
     }
}

user_config定义如下,原来是在keymap.c中定义的,挪到via.c

typedef union {
  uint32_t raw;
  struct {
    bool top_rgb_change :1;
    bool bottom_rgb_change :1;
    bool top_rgb_signal :1;
    bool bottom_rgb_signal :1;
  };
} user_config_t;

user_config_t user_config;

最后为了SignalRGB显示在上层、原版动画显示在下层的效果,需要在开关打开时,原版的RGB不写入,防止冲突。在animation中定义自定义函数rgb_matrix_set_color_user_config,让原版的animation设置RGB时都调用这个函数。

#pragma once

typedef HSV (*dx_dy_dist_f)(HSV hsv, int16_t dx, int16_t dy, uint8_t dist, uint8_t time);

void rgb_matrix_set_color_user_config(int index, uint8_t r, uint8_t g, uint8_t b) {
    if (user_config.top_rgb_signal == false && index < 70) {
        rgb_matrix_set_color(index, r, g, b);
        return;
    }
    else if (user_config.bottom_rgb_signal == false && index >= 70) {
        rgb_matrix_set_color(index, r, g, b);
        return;
    }
    else return;
}

bool effect_runner_dx_dy_dist(effect_params_t* params, dx_dy_dist_f effect_func) {
    RGB_MATRIX_USE_LIMITS(led_min, led_max);

    uint8_t time = scale16by8(g_rgb_timer, rgb_matrix_config.speed / 2);
    for (uint8_t i = led_min; i < led_max; i++) {
        RGB_MATRIX_TEST_LED_FLAGS();
        int16_t dx   = g_led_config.point[i].x - k_rgb_matrix_center.x;
        int16_t dy   = g_led_config.point[i].y - k_rgb_matrix_center.y;
        uint8_t dist = sqrt16(dx * dx + dy * dy);
        RGB     rgb  = rgb_matrix_hsv_to_rgb(effect_func(rgb_matrix_config.hsv, dx, dy, dist, time));
        rgb_matrix_set_color_user_config(i, rgb.r, rgb.g, rgb.b);
    }
    return rgb_matrix_check_finished_leds(led_max);
}

效果:

代码库:
https://github.com/azuse/qmk_firmware

]]>
https://azusebox.moe/2022/08/12/%e4%bb%bb%e4%bd%95qmk-via%e9%94%ae%e7%9b%98%e6%8e%a5%e5%85%a5signalrgb%e5%ae%9e%e7%8e%b0%e7%94%b5%e8%84%91%e7%ab%afrgb%e6%8e%a7%e5%88%b6/feed/ 18 13025
华硕(asus)AI suit3与two way noise cancellation软件下载提取(download link) https://azusebox.moe/2021/12/16/%e5%8d%8e%e7%a1%95%ef%bc%88asus%ef%bc%89ai-suit3%e4%b8%8etwo-way-noise-cancellation%e8%bd%af%e4%bb%b6%e4%b8%8b%e8%bd%bd%e6%8f%90%e5%8f%96%ef%bc%88download-link%ef%bc%89/ https://azusebox.moe/2021/12/16/%e5%8d%8e%e7%a1%95%ef%bc%88asus%ef%bc%89ai-suit3%e4%b8%8etwo-way-noise-cancellation%e8%bd%af%e4%bb%b6%e4%b8%8b%e8%bd%bd%e6%8f%90%e5%8f%96%ef%bc%88download-link%ef%bc%89/#respond Thu, 16 Dec 2021 09:57:03 +0000 http://azusebox.moe/?p=12937 因为armoury crate安装时有一直卡在55%安装不了的问题,自行从amrouy crate中提取了安装程序下载链接,可以自行安装:
AI suit3:
https://dlcdnets.asus.com/pub/ASUS/mb/14Utilities/SW_ASUS_AISuite3_DIP5_TP_W10_64_V30079_20210514RL.zip
Two way noise cancellation:
https://dlcdnets.asus.com/pub/ASUS/mb/14Utilities/SW_AI_MIC_W10_64_V1023_20210106RL.zip

提取位置C:\Users\用户名\AppData\Local\Packages\B9ECED6F.ArmouryCrate_qmba6cd70vzyy\LocalState\SupportTemp

]]>
https://azusebox.moe/2021/12/16/%e5%8d%8e%e7%a1%95%ef%bc%88asus%ef%bc%89ai-suit3%e4%b8%8etwo-way-noise-cancellation%e8%bd%af%e4%bb%b6%e4%b8%8b%e8%bd%bd%e6%8f%90%e5%8f%96%ef%bc%88download-link%ef%bc%89/feed/ 0 12937
小内存机器的一些mysqld优化记录 https://azusebox.moe/2021/05/06/%e5%b0%8f%e5%86%85%e5%ad%98%e6%9c%ba%e5%99%a8%e7%9a%84%e4%b8%80%e4%ba%9bmysqld%e4%bc%98%e5%8c%96%e8%ae%b0%e5%bd%95/ https://azusebox.moe/2021/05/06/%e5%b0%8f%e5%86%85%e5%ad%98%e6%9c%ba%e5%99%a8%e7%9a%84%e4%b8%80%e4%ba%9bmysqld%e4%bc%98%e5%8c%96%e8%ae%b0%e5%bd%95/#respond Wed, 05 May 2021 19:41:58 +0000 http://azusebox.moe/?p=12860 1 一键的mysql优化建议工具

https://github.com/major/MySQLTuner-perl
提出了很多对我的数据库分析的建议,例如:

 Enable the slow query log to troubleshoot bad queries
    Reduce or eliminate unclosed connections and network issues
    Configure your accounts with ip or subnets only, then update your configuration with skip-name-resolve=1
    When making adjustments, make tmp_table_size/max_heap_table_size equal
    Reduce your SELECT DISTINCT queries which have no LIMIT clause
    Set thread_cache_size to 4 as a starting value
    For MySQL 5.6.2 and lower, Max combined innodb_log_file_size should have a ceiling of (4096MB / log files in group) - 1MB.
    Before changing innodb_log_file_size and/or innodb_log_files_in_group read this: https://bit.ly/2TcGgtU
Variables to adjust:
    SET innodb_stats_on_metadata = OFF
    tmp_table_size (> 16M)
    max_heap_table_size (> 16M)
    thread_cache_size (start at 4)
    innodb_file_per_table=ON
    innodb_buffer_pool_size (>= 815.3M) if possible.
    innodb_log_file_size should be (=16M) if possible, so InnoDB total log files size equals to 25% of buffer pool size.

2 减少sleep的mysqld进程

减少wait_timeout: wait_timeout=60 mysql连接空闲60秒就会被关闭,如果有的脚本里不带重试就会出现Mysql has gong away的报错

但是这个是show processlist;中显示的“process”,似乎和linux的nysqld “process”又是两个东西,真正占用内存的是这些mysqld的进程啊。
一般来说mysqld应该是对每个客户端的connection创建一个thread而不是process,这里这么多mysqld应该就是服务启动时候fork出来的一个进程池?有点类似的apache的一个连接一个进程。没查到怎么设置这个进程池的大小,但是对于每一个进程内存相同部分是共享空间的,只有不同部分才占用空间,所以实际的内存占用会比显示的内存小,参考:https://serverfault.com/questions/170541/too-many-mysql-processes

If you're concerned about the memory usage of each mysql process, look at /etc/mysql/my.cnf, and look at the variables in the mysqld section:

key_buffers
thread_stack
thread_cache_size
max_connections
query_cache_limit
query_cache_size

3 修改配置文件my.cnf

[mysqld]
thread_cache_size=20

wait_timeout=60

performance_schema = off
performance_schema_max_table_instances=150
table_definition_cache=150
table_open_cache=64
innodb_buffer_pool_size=2M

tmp_table_size=16M
max_heap_table_size=16M
innodb_file_per_table=ON

max_connections=

* thread_cache_size

线程池缓存大小
( 当客户端断开连接后 将当前线程缓存起来 当在接到新的连接请求时快速响应 无需创建新的线程 )
可以通过如下几个MySQL状态值来适当调整线程池的大小
Threads_cached : 当前线程池中缓存有多少空闲线程
Threads_connected : 当前的连接数 ( 也就是线程数 )
Threads_created : 已经创建的线程总数
Threads_running : 当前激活的线程数 ( Threads_connected 中的线程有些可能处于休眠状态 )

* wait_timeout

连接空闲等待时间,缺省情况下,wait_timeout的初始值是28800
wait_timeout过大有弊端,其体现就是MySQL里大量的SLEEP进程无法及时释放,拖累系统性能,不过也不能把这个值设置的过小,否则你可能会遭遇到“MySQL has gone away”之类的问题,通常来说,我觉得把wait_timeout设置为10是个不错的选择,但某些情况下可能也会出问题,比如说有一个CRON脚本,其中两次SQL查询的间隔时间大于10秒的话,那么这个设置就有问题了(当然,这也不是不能解决的问题,你可以在程序里时不时mysql_ping一下,以便服务器知道你还活着,重新计算wait_timeout时间):

* performance_schema

性能分析工具,MySQL的performance schema 用于监控MySQL server在一个较低级别的运行过程中的资源消耗、资源等待等情况
简单概括就是能检测mysql里是不是有慢查询,长时间锁等性能问题,但同时也会占用一些资源

* tmp_table_size(max_heap_table_size)

临时表的内存缓存大小
( 临时表是指sql执行时生成临时数据表 )

默认值 16777216
最小值 1
最大值 18446744073709551615
// 单位字节 默认值也就是16M多

首先在优化sql的时候就应该尽量避免临时表
如果必须使用临时表 且同时执行大量sql 生成大量临时表时适当增加 tmp_table_size
如果生成的临时表数据量大于 tmp_table_size 则会将临时表存储与磁盘而不是内存

**临时表的大小应该根据自己数据库的使用情况设置,小网站的小数据库就可以开的比较小**

* innodb_file_per_table

独立表空间

Innodb存储引擎可将所有数据存放于ibdata*的共享表空间,也可将每张表存放于独立的.ibd文件的独立表空间。
    共享表空间以及独立表空间都是针对数据的存储方式而言的。
    共享表空间:  某一个数据库的所有的表数据,索引文件全部放在一个文件中,默认这个共享表空间的文件路径在data目录下。 默认的文件名为:ibdata1  初始化为10M。
    独立表空间:  每一个表都将会生成以独立的文件方式来进行存储,每一个表都有一个.frm表描述文件,还有一个.ibd文件。 其中这个文件包括了单独一个表的数据内容以及索引内容,默认情况下它的存储位置也是在表的位置之中。

2.1 共享表空间

    优点:
    可以将表空间分成多个文件存放到各个磁盘上(表空间文件大小不受表大小的限制,如一个表可以分布在不同的文件上)。数据和文件放在一起方便管理。
    缺点:
    所有的数据和索引存放到一个文件中,虽然可以把一个大文件分成多个小文件,但是多个表及索引在表空间中混合存储,这样对于一个表做了大量删除操作后表空间中将会有大量的空隙,特别是对于统计分析,日值系统这类应用最不适合用共享表空间。

    2.2 独立表空间

    在配置文件(my.cnf)中设置: innodb_file_per_table
    优点:
    1.每个表都有自已独立的表空间。
    2.每个表的数据和索引都会存在自已的表空间中。
    3.可以实现单表在不同的数据库中移动。
    4.空间可以回收(除drop table操作处,表空不能自已回收)
        a.Drop table操作自动回收表空间,如果对于统计分析或是日值表,删除大量数据后可以通过:alter table TableName engine=innodb;回缩不用的空间。
        b.对于使innodb-plugin的Innodb使用turncate table也会使空间收缩。
        c.对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理。
    缺点:
    单表增加过大,如超过100个G。
    相比较之下,使用独占表空间的效率以及性能会更高一点。

* table_open_cache

table_open_cache指定表高速缓存的大小。每当MySQL访问一个表时,如果在表缓冲区中还有空间,该表就被打开并放入其中,这样可以更快地访问表内容。
通过检查峰值时间的状态值Open_tables和Opened_tables,可以决定是否需要增加table_open_cache的值。
如果你发现open_tables等于table_open_cache,并且opened_tables在不断增长,那么你就需要增加table_open_cache的值了(上述状态值可通过SHOW GLOBAL STATUS LIKE ‘Open%tables’获得)。
注意,不能盲目地把table_open_cache设置成很大的值,设置太大超过了shell的文件描述符(通过ulimit -n查看),造成文件描述符不足,从而造成性能不稳定或者连接失败。

* table_definition_cache

理解下来,就是控制总frm文件的数量,还是个hash表,内部维护。如果打开的表实例的数量超过了table_definition_cache设置,LRU机制将开始标记表实例以进行清除,并最终将它们从数据字典缓存中删除。

简单通俗点frm文件有多少,就设置多少了。

* max_connection

Mysql允许的客户端最大连接数,每一个客户端执行connect就算一次链接,当链接数达到max_connection时,会出现MySQL: ERROR 1040: Too many connections错误;
查询当前链接:SHOW PROCESSLIST
在连接不活跃时间超过wait_timeout时,这个connection会被自动kill,客户端就会报mysql has gong away需要重新连接,wait_timeout的设置根据业务确定,但是注意客户端一定要加重试,不然直接连接断开就error了
推荐max_connection配置为最大链接数占比85%,可以用show global status like 'Max_used_connections';查询历史中的最大连接数:

MySQL [(none)]>  show global status like 'Max_used_connections';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| Max_used_connections | 2583  |
+----------------------+-------+
1 row in set (0.01 sec)

参考:
mysql中有大量sleep进程的原因与解决办法
减少mysql内存_减少mysql内存占用
MySQL 优化之 thread_cache_size
mysql innodb_log_file_size 和innodb_log_buffer_size参数
MySQL服务器最大连接数怎么设置才合理

]]>
https://azusebox.moe/2021/05/06/%e5%b0%8f%e5%86%85%e5%ad%98%e6%9c%ba%e5%99%a8%e7%9a%84%e4%b8%80%e4%ba%9bmysqld%e4%bc%98%e5%8c%96%e8%ae%b0%e5%bd%95/feed/ 0 12860
名古屋大学情报系统秋入试体验 https://azusebox.moe/2020/09/03/%e5%90%8d%e5%8f%a4%e5%b1%8b%e5%a4%a7%e5%ad%a6%e6%83%85%e6%8a%a5%e7%b3%bb%e7%bb%9f%e7%a7%8b%e5%85%a5%e8%af%95%e4%bd%93%e9%aa%8c/ https://azusebox.moe/2020/09/03/%e5%90%8d%e5%8f%a4%e5%b1%8b%e5%a4%a7%e5%ad%a6%e6%83%85%e6%8a%a5%e7%b3%bb%e7%bb%9f%e7%a7%8b%e5%85%a5%e8%af%95%e4%bd%93%e9%aa%8c/#comments Thu, 03 Sep 2020 04:09:25 +0000 http://azusebox.moe/?p=12753

我无语了,我准备了一个月的编程题和离散题,最后却问了我算法和命题的概念

2020年的博士前期情报系统秋入试,平心来说绝对是最近很多年来名大情系考的最简单的一次了;怎么个简单法,考试前提前告知:删去了以前的概率统计部分(等于名大不考跟高数有关的任何数学),并且增加了计算机网络、操作系统、算法,考的都是概念,基本不含任何计算,但是时间紧迫,基本没有时间想,看到题目不能张口就说那基本这题就没了。

口头面试的流程大概是下午一点报道(JST),然后和几个同面试场的考生在等待室里闭麦等着,大概一点四十五分监考会说你可以断开链接,去连接考场的会议室了,从等待室里退出来以后就直接输入之前抄的考场会议室号码,连上之后等到两点开考才会放你进去。

考试两点钟正式开始,是我报的第一志愿监的考,上来先简单和你介绍一下怎么答题:一会会给你看11题,你要从当中挑三道,但是挑完之后就不可以更换,挑完之后我们会一道题一道题问你,你答完的题目不可以再回去修改,问题主要通过口头表述,放在PPT的大概就是个问题的方向,你可以用纸来解释,一题大概之后五分钟的时间,之后还有7分钟是问一般问题。

離散数学、オートマトン形式言語、アルゴリズム、論理学、コンパイラ、プログラミング、論理設計、計算機アーキテクチャー、オプレーティングシステム、情報ネットワーク、ソフトウェア設計法

明白了之后就是放题目,11题在屏幕上摞了两列,左边一列主要是离散数学的概念,还有软件开发的概念?,以及计组的概念题,都是很短的题目,几个字;右边放了一段代码:

int combine(int n, int m){
    if (m==0 || n==m){
        return 1;
    }
    else{
        return combine(n-1, m) + combine(n-1, m-1);
    }
}

这段代码计算的是组合数,我当时并不知道,知识因为之前做过去问,编程题往往都能做出来,所以准备代码题无脑选上,然后再选离散和操作系统。用了大概几十秒把11题扫了一遍,排掉我看不懂的片甲单词,排掉看得懂但是不知道是啥的汉字,最后选了【命题公式的可充分性证明】,【PAGE FAULT】和【combine,上面那段代码】

考官让我挑一题入手,以往经验代码题最简单,我便说了11题。之后考官就直接问了,“这段代码的作用是什么”。我才刚刚开始读代码,把代码扫了一遍,似乎是递归从n加到m,又从n加到m-1,这到底计算了什么?正在想的时候,面试官说想不出来可以先问你下一小问,这个问题一会还可以回答。哦,那只能先下一小问了,没时间了。下一小问很简单,“这个函数使用的自己调用自己的方法叫什么?”迭代调用啊,迭代的日文是什么????迭代的英文是什么????好像是it…iter….iterarative?我含糊不清的说出这个单词,面试官说没听清,我慌忙写下“迭代”两字,考官看不懂笑了,这尼玛写的中文吧(迭代日文是イテレータ),最后只能莽的说了一个iterative重音还发在了it上,十分滑稽。(其实根本就不是迭代,是递归,recursive,再帰。) “那行吧,你能不能告诉我他的复杂度是什么?用n来表示。” 其实我根本没听懂他问的是什么,那个关键的词没听懂,但是隐约感觉是要问复杂度了。这个算法的复杂度,递归调用自己那是不是n的平方,所以我瞎几把写了O(n^2) (其实之前还在纸上算了一会这函数的内容,造成这个时候已经没有时间再在这道题停留了,其他监考老师在催下一题。),放到镜头前给他看,“n二乗ですね。”之后他又问了我一下有没有想出来这个函数是做什么用的,我写了一个\sum_{i=1}^n m-i给他看。

其实这都是错的,复杂度是O(C_n^m)计算的结果是\frac{n!}{m!(n-m)!}最后的那个return是组合数公式:
C_n^m = C_{n-1}^m + C_{n-1}^{m-1}

\tbinom{n}{m}=\tbinom{n-1}{m} + \tbinom{n-1}{m-1}


第一题做完之后,花了很多时间,根本没做出来,考官让我快点,剩下两题里先挑一个有自信的。我果断选了内存缺页,“介绍一下内存缺页是什么吧”,我本来就比较熟悉操作系统,上午刚好复习了内存缺页,就用英文解释了一下,程序要访问逻辑地址的时候,逻辑地址的内存不在实际的物理内存中,CPU报一个缺页中断(interrupt我说了error),然后去硬盘或者SWAP里把虚拟内存搬到实际的物理内存里,之后提供程序正常访问内存。之后又问了一下,你能不能讲一下逻辑地址是怎么转换成物理地址的?这个上午刚刚看过课件,我说是用逻辑地址去页表里查物理地址,然后简单画了个逻辑页面->页表->物理页面的图,之后又问我能不能再讲的详细一点?我说我不知道了,然后就下一题了。


最后一题,完全就是踩坑了,这个命题的可充分性,应该不是我想的那个成真赋值就可以的可充分,他问我你说说怎么证明可充分性,我说有两种方法,一是列真值表(Truth Table),另一个是通过公式,将原方程变换成另一个可以证明成立的方程;他问怎么变换呢,变换成什么形式呢?我想说变成最小合取范式,根本不记得这玩意英文怎么讲,就记得什么DFD CFN之类的缩写,(合取范式 Conjunctive normal form 連言標準形れんげんひょうじゅんけい 析取范式 Disjunctive normal form 選言標準形 せんげんひょうじゅんけい)最后支支吾吾啥也没说出来,他说那好吧那你说的另一种方法是什么?我说列真值表,true table,列出所有可能的情况,看看有没有一种情况下公式成立。

离散数学以前卷子都是算术题,我以为他会给我个公式让我去写一写证明公式可充分,结果一点公式都没有,全是嘴上讲的,这谁知道怎么讲啊,都是看的中文资料,偶尔看过几个CNF DNF之类的那种紧张的情况下也想不起来,youtube上的离散数学还都是印度咖喱英语,听都不想听/


三题全问完之后就是问一些入学的,你为什么想来,你愿不愿意接受调剂,你是不是报了别的学校,你毕业是升学还是入职

之后让你抄下考完试等待室的号码,之后断开会议,加入等待室会议。

等待室里就是日本老哥互相瞪眼,百般聊赖,比较尴尬的是日本人都穿了西服领带,至少至少也是件白衬衫,我就穿了个T恤,真的太尬了,T恤领子还很大,比较旧的T恤(。

总结来说,这次一是算法不精,那个算法呢,那个时间下,以前没看过的肯定是没法推导出来的,不过如果数学熟悉,可能能一眼看出来那是个组合数递推公式?但是我早忘了组合,以为不考数学就也没复习,没想到这种形式上还是考到了。如果能看出来是递推公式,每次+1应该很容易想出来复杂度等于组合数。内存缺页答得还行,从逻辑地址转物理地址的详细过程不知道,今天可以去搞清楚。离散数学命题那题完全就是踩坑了,wdnmd,谁知道会一点公式都没有,全都是讲的,然后全都不会讲,下次复习一定一定要见到所有中文词和符号吗,都背出来对应的日文和英文,我寻思你要去日本上课的,符号都不会说,那还学个几把。

]]>
https://azusebox.moe/2020/09/03/%e5%90%8d%e5%8f%a4%e5%b1%8b%e5%a4%a7%e5%ad%a6%e6%83%85%e6%8a%a5%e7%b3%bb%e7%bb%9f%e7%a7%8b%e5%85%a5%e8%af%95%e4%bd%93%e9%aa%8c/feed/ 3 12753
Neo4j 自带算法库 学习笔记 https://azusebox.moe/2020/05/13/12736/ https://azusebox.moe/2020/05/13/12736/#respond Tue, 12 May 2020 16:25:47 +0000 http://azusebox.moe/?p=12736 Neo4j 自带算法库 学习笔记

Graph Algorithms 库

算法1 The Common Neighbors algorithm

通用邻居算法

计算公式:
其中N(x)是与节点相邻的节点集x,N(y)是与节点相邻的节点集y。

公式相当于计算从节点x与节点y的相邻节点的交集,等于从x出发经过两跳到达y的路径数

创建一个示例图:
MERGE (zhen:Person {name: "Zhen"})
MERGE (praveena:Person {name: "Praveena"})
MERGE (michael:Person {name: "Michael"})
MERGE (arya:Person {name: "Arya"})
MERGE (karin:Person {name: "Karin"})

MERGE (zhen)-[:FRIENDS]-(arya)
MERGE (zhen)-[:FRIENDS]-(praveena)
MERGE (praveena)-[:WORKS_WITH]-(karin)
MERGE (praveena)-[:FRIENDS]-(michael)
MERGE (michael)-[:WORKS_WITH]-(karin)
MERGE (arya)-[:FRIENDS]-(karin)

返回Michael和Karin的共同邻居数:
MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.commonNeighbors(p1, p2) AS score

执行
MATCH (p1:Person {name: 'Zhen'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.commonNeighbors(p1, p2) AS score

输出
2.0

执行
MATCH (p1:Person {name: 'Michael'})
MATCH (p2:Person {name: 'Karin'})
RETURN algo.linkprediction.commonNeighbors(p1, p2) AS score

输出
1.0

算法2 Weakly Connected Components algorithm

弱连接组件算法

WCC算法在无向图中找到一组连接的节点,其中同一组中的所有节点形成一个连接的组件。通常在分析的早期使用WCC来了解图形的结构。

算法3 Louvain算法

Louvain社区检测方法是一种用于检测网络中社区的算法。它最大化了每个社区的模块化评分,其中模块化量化了节点向社区的分配质量。这意味着,与社区在随机网络中的连接程度相比,评估社区中节点的连接密度更高。

CALL algo.beta.louvain(label: STRING, relationship: STRING, {
 write: BOOLEAN,
 writeProperty: STRING
 // additional configuration
})
YIELD nodes, communities, modularity, loadMillis, computeMillis, writeMillis

Louvian算法就是基于模块度Q的社区划分算法,简单的说,就是一组构成社区的节点,节点之间连接(社区内)越多,节点与外部连接(社区外)连接越少,模块度Q就越高。

通过Louvain获取一个社区里的全部节点和边:
call algo.louvain.stream() yield nodeId, community match (n) where id(n)=nodeId and community=1 with collect(n) as ns match (p)-[r]-(q) where q in ns and p in ns and q <> p return q,p,r;
直接调用Louvain
call algo.louvain.stream()
]]>
https://azusebox.moe/2020/05/13/12736/feed/ 0 12736
日本大学院研究企划的写法——2 https://azusebox.moe/2020/03/30/%e6%97%a5%e6%9c%ac%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e4%bc%81%e5%88%92%e7%9a%84%e5%86%99%e6%b3%95-2/ https://azusebox.moe/2020/03/30/%e6%97%a5%e6%9c%ac%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e4%bc%81%e5%88%92%e7%9a%84%e5%86%99%e6%b3%95-2/#respond Mon, 30 Mar 2020 06:19:09 +0000 http://azusebox.moe/?p=12720 【转自北陆先端大学:http://www.jaist.ac.jp/~as-asami/exam_research_plan/exam_research_plan.html】

受験予定のみなさんへ 入試前から研究はスタートする 研究計画書の書き方

入試では、今までの実績や研究の経過を説明するほか、最も大切なことは研究計画を提示して、自分の研究に理解を得ることです。また、研究することができる知識やスキルを、受験生として十分持っていること示すことも、研究計画書の重要な役割です。そこで、このページでは、大学院入試で重要な「研究計画書の書き方」を説明することにします。
なお、本学、北陸先端科学技術大学院大学を受験するのであれば、事前に計画書の作成について教員や先輩の学生たちに相談することもできます。(敷田)に相談もできます。そ場合は下記のメールから(敷田)まで連絡して下さい。

研究計画書を書く目的は?

入試、特に大学院の入試のために作成する「研究計画書」の第一の目的は、「面接で試験担当(面接)者とコミュニケーションをとる」ことです。ですから、どれだけ内容が素晴らしくても、自分にしか解らない独特の研究計画書を書いては、その目的が達成できません。
 そこで、一方的に自分の優秀さや実力を表明する計画書ではなく、入試の面接者とできるだけ研究を共有し、そこから自分の研究の面白さや可能性を説明できればよいと考えて研究計画書を作成してはどうでしょうか。

 大事なことは、研究計画書を入試における「コミュニケーション素材」と考えることです。もちろん、事前審査でも、計画書が客観的に評価されると思いますが、自分の研究の可能性を面接者と共有する点では同じです。

受け取って困る研究計画書とは

研究計画書の相談の際には、「困った研究計画書」をよく見ます。それは、「○○を研究する」とか「○○を調査する」とだけ書かれていて、具体的な研究内容、つまりどのような方法や手法で研究するか、またそのプロセスが書かれていない研究計画書です。面接では「じゃあどうやって研究するの?」と聞かざるを得なくなって、それ以上の展開はありません。。。。

また、「地域や地域再生を研究したい」という希望を持っている学生の場合、時として、「観光客を増やすことを研究する」「地域再生を実現する」など、具体的な問題の解決策を提案するという「実践的な内容」を書いてきます。しかし、それは研究ではなく、実践して問題解決した方がよい内容です。つまり、わざわざ研究しなくても、明日からでも個人的に努力することができます。

どうしても「観光客の増加や地域再生がやりたい」、つまり「実践」がやりたいのであれば、大学院ではなく実社会に出て、また自治体やNPOに勤めてするほうが現実的です。わざわざ大学院で学ぶことをお勧めできません。

特定の課題を解決する研究をしたいという強い希望がある場合を除いて、「なぜそうなったのか」「どういうプロセスでそうなったのか」などの理由を説明したり、起きている現象に新しい意味を見いだしたりすることが研究だからです。そのため「実践的」な問題解決の計画書を書いても、研究するよりも、社会で解決を実践してはどうかと思われてしまいます。

 つまり、具体的な問題の解決は、わざわざ大学院で研究しなくても、実社会で実践することを勧めます。もちろん、課題解決自体が研究であるという工学部のエンジニアリングのような分野もありますが、それは背景でしっかりその必要性や解決方法の新規性、解決方法を確立するための課題解明の独創性などを説明することが求められます。

研究計画書の基本

研究計画は最低、「Why, How, What」を含んで書いて下さい。

Why-なぜあなた(私)はこの研究をするのか
How-どうやって研究をするのか
What-何を研究するのか

まず、自分の研究の目的が何かをはっきりさせ【目的=What】、その次になぜこの研究をするのかを書きます【背景=Why】。さらに【方法=How】です。以上の3つの内では、Whyが一番重要です。しかし3つのうちのどれが欠けても研究計画書としては成り立ちません。

研究計画書はどう構成するのか?

以上のWhat, Why, Howを意識しながら、研究計画書は、以下の構成で作成するとわかりやすいと思います。それは、

研究のタイトル(題目、テーマ)
この研究の目的
この研究の背景
この研究で採用する方法
この研究の結果から予想される成果
研究の特徴

まず、自分の研究の目的が何かをはっきりさせ【目的】、その次になぜこの研究をするのかを書きます【背景】。そして次に、どのようにして研究するのか【研究方法】。最後に【予想される結果】と【研究の特徴】を書きます。

計画書の各論に挑む

それでは各論に入ります。
* 研究のタイトル(題目)
研究のタイトル、あるいはテーマといってもいいでしょう、はこれから研究計画書で説明すること全体の『キャッチコピー(セールスコピー)』です。つまり「これについて書く」という宣言のようなものです。もちろんタイトルがすべてではありませんが、これでかなり決まってしまいます。そのためには、読み手、面接官が興味を持ってくれそうなタイトルにしましょう。要するに、相手が、「それって面白そう!」と思ってくれるようなキャッチコピー感覚で創るのがコツです。
 
 なおタイトルいったん決めたら変えられないということではなく、入学後の議論や調査、研究の進展によっては変わります。それは入学後に受験生の皆さんが前進したことの証しでもありますから、心配いりません。実際、変わることが多いのが現実です。

]]>
https://azusebox.moe/2020/03/30/%e6%97%a5%e6%9c%ac%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e4%bc%81%e5%88%92%e7%9a%84%e5%86%99%e6%b3%95-2/feed/ 0 12720
日本大学院研究企划的写法——1 https://azusebox.moe/2020/03/30/%e6%97%a5%e6%9c%ac%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e4%bc%81%e5%88%92%e7%9a%84%e5%86%99%e6%b3%95-1/ https://azusebox.moe/2020/03/30/%e6%97%a5%e6%9c%ac%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e4%bc%81%e5%88%92%e7%9a%84%e5%86%99%e6%b3%95-1/#comments Mon, 30 Mar 2020 04:14:00 +0000 http://azusebox.moe/?p=12719 【渣翻译文】原文地址:https://例文.net/archives/project.html

研究计划的写法与例文

我把实际提出的研究计划书,内容的每一个方面都详细的介绍了一遍。
评论中记录了写作的要点。
请自由利用下面的文章。
例子用的是文科的企划书,但是理科生也有可以从中借鉴的部分。

研究企划书

○○大学○○学部〇〇学科〇〇専攻○年 氏名

研究主题:关于○○○○○的▼▼▼

评论:最好选择在大学院的教授里是这方面专家的题目。如果有特定想让哪个教授指导你,就要选择和那个教授的研究分野有关的题目。不然的话,「我们的大学院,没有能指导这个题目的教授哦?」,「为什么这个题目,非要在我们大学院学不可呢?」,面试的时候就会问到这样的问题。

主旨

近年的・・・・・(产业)的・・・・(研究)研究陷入困难的原因其中之一是・・・・(问题)。本研究将对国内的・・・・(产业)的・・・・(具体题目)和・・・・(具体题目)的现状予以调查,从中把握现状并提出课题。并且,也可以讨论针对・・・・问题的对应策略。

评论:大概简介一下要点

背景

就像现在・・・・(研究者、大众)所看到的,・・・・(产业)在近年进步飞跃,在今后会形成新的产业链的,应该非〇〇〇〇〇莫属。
日本在以前,由于・・・・・・・・・的契机,产生了・・・・・・的重大变化。
现在人们所说的“・・・・・・”,・・・・・・就是在那个时期开始被人们所逐渐重视。这个时期是・・・・・样的

评论:以前这个研究领域是什么样的,写一些「事实」

但是就在最近几年,围绕〇〇〇〇〇的▼▼▼环境在急剧恶化。例如,观察・・・・・的现状,就可以看出・・・・・・。受到这个情况的影响,・・・・・・・和・・・・・・・・都有下降的趋势。

另外,调查〇〇〇〇〇的・・・・的现状,・・・・和・・・・都很少,・・・・和・・・・也在经历顶峰之后呈现出下降趋势。

现在,・・・・的规模在美国有・・・・、在欧洲有・・・・,而与之相对在日本仅有・・・・。再加上欧美正在对・・・・进行・・・・(改进)。

评论:经过了什么样的变化,现状又是什么样的,描述「事实」。最好标上角标,写出来是从哪里引用的这些变化。

也就是说,日本的现在的〇〇〇〇〇的▼▼▼在国际竞争中处于极其不利的状况。

这个问题,例如,在△△△的报告中也有提到。

评论:指出现状中的问题。并且,表现问题的方法有:由于〇〇〇,▲▲▲困难重重;和:如果能实现〇〇〇,▲▲▲将大幅发展;两种。在此例中使用的是前者。

考察

本来在日本的高水平的基础研究中,〇〇〇〇〇问题应该被・・・方法解决的。
但是,因为有・・・,本来如果用・・・方法的话,・・・问题就能很快被解决。

评论:为了解决问题,本来人们提出的方案是什么,你的「意见」是什么,以及你的理由。

但是,这个・・・没有取得积极成效的原因,其中一部分是由于〇〇〇〇〇的・・・,没有被很好的判断;或是由于〇〇〇〇〇中・・・的研究还不充分的原因。这件事可以从〇〇〇〇〇的・・・这个事实中看出来。

评论:是由于什么原因发生的问题,写出你的分析结果

目的

到目前为止,关于〇〇〇,・・・的状况,或是・・・,都有很多的报告。
例如,在关于・・・的报告中,基本都有提到△△△。又或是,在・・・的报告中,基本都有提到▽▽▽。
但是,实际上▼▼▼▼▼对〇〇〇〇〇的・・・进行的是什么样的评价,评价的过程是怎么样的,找不到可以作为课题的报告

コメント:問題の原因の解消方法を検討した先行研究が調べた限りでは見当たらなかったと記載するか、あるいは、先行研究の抱える問題点を記載します。この例文では、前者を採用しています。

そこで、本研究では、○○○○○にとって主な・・・・手段である・・・・・・・・・及び○○○○○の行う・・・・・・・への取組みについて現状の把握と課題の抽出を行い、解決策を検討することを目的とする。

コメント:問題の解決策を検討することを目的とします

調査対象および方法

  • 1)・・・・・に対するアンケート調査
    国内の・・・・・について、・・・の分野の主要な企業に対して、○○○○○の・・・・・・を取り巻く実態及び企業としての意見を調査することを目的として、各企業の・・・・・・・・に質問調査表を送付し、回答を依頼する。
     質問は、(1)~(2)~(3)~(4)~(5)~(6)~、などの項目を中心に行い、回答を得る。

  • 2)○○○○○に対するアンケート調査
    ○○○○○は、・・・・・・・を・・・・しているが、・・・・を向上させるためにどのような積極的活動を行っているのか、その実態及び企業としての意見を調査することを目的として、上場している企業を含めた○○○○○の30社程度に質問調査表を送付し回答を依頼する。
    質問は、(1)~(2)~(3)~(4)~(5)~(6)~、などの項目を中心に行い、回答を得る。

コメント:時期、対象、方法、費用など、できるかぎり具体的に書きます。詳しく書くほど、計画性や実行可能性が評価しやすくなるからです。「これ本当に在学期間中に実行して完了できるの?」と面接で聞かれる恐れのあることは書かないようにします。

期待する成果

本研究により、○○○○○と・・・・・の双方の観点から・・・・・における・・・・・・・・の現状の諸問題を明らかにできることを期待し、今後の対応策を検討したい。そして、その研究結果を調査協力してもらった各企業にフィードバックすることにより、本研究が○○○○○を取り巻く資金循環の改善に役立てられれば幸いである。

コメント:目的を果たした研究によって、社会にどのような影響が与えられるのかを書きます。これは、受験者が、いったいどのようなビジョンを持って、その研究を行おうとしているのかをアピールするための項目です。なお、この項目は大げさなものでなくて良く、他の研究者に新たな知見を提示することで研究の発展に寄与したいといった程度のものでも構いません。

参考資料

1)・・・・・・・・
2) ・・・・・・・・

コメント:参考文献は必ず記載します。注意すべきことは、参考文献は、数が多ければいいというものではないことです。また、どんな文献でもいいというものではなく、研究テーマの分野の有名な研究者の書籍や論文である必要があります。なお、参考資料については、このように最後にまとめて記載することもできますが、マイクロソフトのワードで研究計画書を作成するのであれば、脚注の機能を使って書きましょう。

下次再翻,翻译太花时间了

]]>
https://azusebox.moe/2020/03/30/%e6%97%a5%e6%9c%ac%e5%a4%a7%e5%ad%a6%e9%99%a2%e7%a0%94%e7%a9%b6%e4%bc%81%e5%88%92%e7%9a%84%e5%86%99%e6%b3%95-1/feed/ 1 12719
【Desk Review】桌面评测 https://azusebox.moe/2019/11/06/%e3%80%90desk-review%e3%80%91%e6%a1%8c%e9%9d%a2%e8%af%84%e6%b5%8b/ https://azusebox.moe/2019/11/06/%e3%80%90desk-review%e3%80%91%e6%a1%8c%e9%9d%a2%e8%af%84%e6%b5%8b/#comments Tue, 05 Nov 2019 23:49:23 +0000 http://azusebox.moe/?p=12513 我很喜欢我的桌面布置,里面基本上积攒了三年里的点点滴滴的回忆。能再坐在这张桌子前的时间也不多了,所以写一篇详细的桌面布置报告,记录一下桌面上的东西,和东西背后的故事.

start with dual screen setup, 两块u2414h,一块是大一买的,一块是室友大二买的,大三室友去了日本,屏幕没人用了我就嫖了玩双屏(bgm38)虽然是双屏开发效率高,却很少用来写代码(越来越懒),基本都是一个屏打游戏同时另一个屏看视频,还有一个屏打游戏一个屏打ps4(bgm38)
左边的屏幕是自己的,用了三年多,中间坏了一次,戴尔售后来换了一个翻新,现在这个翻新又快坏了。。有的时候会闪屏,拔电重插才会好,希望不会彻底毁掉(flag)三年估计也过保了,坏掉还要花钱去换个高刷新的p2417之类的(单屏幕怎么用)
屏幕上面放着最早做的两艘船,2018年2月和2017年10月左右做的,本意是练手用,练一练喷漆、笔涂,左边的是响爷,右边是白露,积了很多灰。pitroad时雨+套改大概要三百块,有点贵就做了白露假装是时雨老婆了。

相信屏幕后面的原画肯定很抢眼(bgm38)大一大二的时候热衷于玛莎哆啦,在骏河屋捡垃圾,捡来的小圆特典原画,稀有度不是很高,本身是剧场版总集篇+新篇观影特典,卖的也很便宜,三四十块。一大包剧场包总集篇的op的原画,shaft的一个大纸袋包着,纸袋上写了制作人员,负责哪卡的原画师、演出、作监,非常真实(就是制作现场拿出来的复制啊!!!)骏河屋上买二手原话的很多,大多都很贵,捡垃圾找了好久,这品质真的惊到我了。而且阴差阳错买了两份,多出来的一份就被我贴在了墙上(代替墙纸),学校这墙本来挺脏的,不想一直盯着脏墙壁嘛

中割表,作豚高潮,我照着成品动画一帧一帧比对过(bgm38)

捡垃圾的时候还一同买了白箱的原画,相比之下白箱的复制原画制作就很辣鸡。小圆用的是原画纸(打孔的),白箱的就是普通a4纸。不过武藏野的这个纸包还是很有收藏价值,里面是剧情里他们反复修改的那几个卡的原画,经典的演出效果教学(bgm38)

为什么会开始捡垃圾,还是因为以前对naoto太太的狂热(https://twitter.com/naoto_moni )高中的时候每天都会逛p站(现在是逛tw),无法自已地喜欢上了这位画师的风格,最早应该是因为这个猫娘

当时找遍了互联网,没有扫图。eh上问了一个经常传naoto扫图的uploader,然后他就扫了,当时ehv还没现在这么多人用吧,评论里中国人也很少。不过估计那个扫图的也是中国人,看懂了我的中文回复,虽然之后回复我都是英文。反正我当时是惊了,这个猫耳娘实在是太太太可爱了。这种有点水彩的上色,纤细又肉感的身体线条,水汪汪的瞳孔,让我沉迷其中无法自拔。大学以后有了玛莎哆啦,有了买本子的途径,疯狂的收集太太的本子和新刊。之后找comike代购,收集场贩。这样持续了一年多,外币新规、海关新规等等出台以后,我熟悉的那家代购现在基本休业了。其他comike代购普遍很贵,动辄0.8 0.9汇率,还加一百块排队费,对一个压缩生活费买本子的人来说实在是太贵了。不过贵有贵的好,这些代购人手足,买到的几率高,也是花钱买保证吧。comike代购实在是太赚了。c96太太场贩我记得是毛巾,四处打听代购,基本买下来价格要翻一倍多,而且因为新展馆启用,comike的位置怕不是增加了很多,很多5 6w关注的画师原来在岛的都挪到了壁。好事呀,可是代购一个岛代购费30,一个壁100,我:(艹)。而且本来室友说必去cm,直接帮我买,结果一周前说不行太忙了去不了(死现充),我本来想的稳得这次终于不用给代购赛钱了,结果。。。最后c96就第一次没有搞太太的场贩了,从c90开始,到c95,六届,三年,狂热褪去,虽然太太的画还是一样可爱,但是我实在没有那么多钱再往这塞了。。。


太太的时雨本


三本mimi,一本被塞到了相框里


老本子,舰娘刚出来(出来以前)太太画的初音本


comi1上发的原创本子

naoto出道这么多年,大概从10年comike开始出本子,我基本买齐了骏河屋是所有能买的旧本子,有dear01 02价格翻了三四倍就没买。然后从costum&costum开始就是直接买的通贩&场贩。

一个画师活跃的时间能有几年呢?一个用业余时间画画的同人画师活跃的时间又能有几年呢?每个人都有自己的生活,生活所迫,放弃同人事业是再正常不过的,活动次数逐渐减少,直到最后活动终止…作为粉丝,并没有什么能做的。我告诉自己不能在一棵树上吊死,不停的去关注新的画师,这也是为什么c96没有再去买场贩的原因之一。

太太人很好,小小的很可爱,c90在日本,二日目吧?进场第一件事就是去太太的摊位,实在是太害怕了,没有抬头仔细看太太一眼。太太的摊位不用排队,前面的人要了签名,我也跟着说了一句サインおねがい,太太问我签在第一页可以吗,本子的左开的,我习惯的反到右开书的第一页(也就是这本本子的最后一页),太太奇怪的看了我一眼:“?え、、ここで???”,然后我才反应过来,赶紧翻到首页。我还记得太太在签字的时候我在站着等,真的是紧张死了,想说点什么,又不知道说什么好,签好名之后太太把本子给我,我习惯性的道出ありがとう,之后便转身逃开(我也不想啊)。依稀记得太太的瘦瘦小小的,但是笑得很可爱,记忆也模糊了,我当时根本不敢正眼仔细看一眼,真是太怂了…


c90上的签字


c90的场贩,扇子


挂画,大概是c94的场贩;前面的时雨私服,2018年6月出荷;秋月是在名古屋买的景品1000円;小号手的t34全内构,也是练手的,不过感觉涂的还不错


c92上的签字(同学帮我带的)


C93的月历,过了一年才在骏河屋上买到。卡套自己印的

真的太喜欢naoto了…

说到这天二日目,我还想起和我一起去的同学hhhhhhhh,他看到一个买饮料的摊位上的小姐姐,戳到了他的萌点,拉着我要去给她们拍着,随便盗摄不礼貌,那同学纠结了好久好久,然后去和小姐姐说可以拍下这个饮料摊,小姐姐当然是那种和蔼可亲的大姐姐,短发,然后当然就同意,他终于如愿以偿拍到了小姐姐hhh,小姐姐在镜头中央。

gal左边的一打本子,gal是入坑作,本命,喜欢ゆきえ的画风,不过剧本是太白开水了


其他本子 kantoku和わき都是很早就喜欢的画师,ゆ吉等等一票大学以后关注的画师也买过本子

切个话题


学长送的扫描仪,(用来扫本子)。和基友的ps4,大一开始借我玩,中间辗转基友,基友女票和我之间,没被快递搞坏真是奇迹。在这上面玩的看门狗2、大表哥、战神,最早说要来玩重力眩晕2(高中借基友psv玩的1代)结果2到现在也没通关(就是没1好玩嘛,战斗太无聊了)

tada68键盘,现在的主力键盘,没有换的打算。我的第一把键盘是10元键鼠包邮的牧马人?大概,用了半年以后换了ikbc青轴(狗店家,双十一下的单定制ikbc,到12月底也没发货),大二到嘉定花了tada68,ikbc入门也不错,不过tada68这个复古pda键帽真的看了长草,唯一缺点是不透光,晚上看不到。

夹子妹地毯,作为深度彩六玩家,值得拥有

左手放的ps4,右手放的台式机,这是什么生活啊…8086k(50周年抽到的)+gtx2060,机箱积灰很多,rgb晚上光污染影响睡觉,主板买错没有支持rgb sync,所以风扇和冷头的rgb都手动关掉了(拔线),只有能软件控制的内存(海盗船)rgb开着,海盗船这rgb是真的好,想买一套指挥官。

50周年抽奖送的8086k,第一次中这么大奖,6月份通知,8月份快递到学校,次年3月才攒够钱装机,水冷冲全核5.1G。
intel新入职礼包里的眼镜布,日用。
感觉和intel缘分不小,也是感受到intel福利真的好,有钱有市场,坐着把钱赚(快动啊牙膏厂)。大一创客比赛,人人送一个edison,大二抽到8086k,大三在亲友介绍去intel实习了半年,怎么说呢,我太社恐了吧,和同事交流都不多,只是自己写代码&摸鱼,然后项目中途也被甲方扔了,只有我一个人继续做一做和一个同事管我这个样子,和其他人交流不多,在最好的环境里。。。


50周年纪念款Badge,戴着个款式的就知道是新员工2333老程序员都是老款胸牌


2016年GDD,在国际会议中心,有排面.jpg


在名古屋交换的两周时间,每天放学都去名古屋站前逛指南针和虎穴蜜瓜,吃个晚饭,一路逛到大须的mandarake。除夕是在蜜瓜虎穴过的(一个人)


去年去的骇客松,名字叫什么都行,和主办方解释了是“叫什么都行”,不是叫“什么都行”,骇客松就是一帮高中同学去玩,高中都是会搞geek的人,大学以后进入不同方向发展,几年之后再一起搞,跨专业结合,各自分工,比高中那会强太多

以前活动还挺丰富的,托此之福绩点惨不忍睹。大三之后基本闭关学习2333要读研的嘛

大一骇客松上拿的edison加开发套件,用来做个iot时钟,我一直想有个桌上时钟,guess it’s a geek solution。python开发,lcd.write(),lcd.setCursor()直接调,很方便。直接通过NTP授时,绝对和电脑手机同步。关键!!是它的第二行,可以自己加各种功能(笑),我往上加过:()显示路由器网速。()自动预约图书馆自习室。()显示寝室剩余电费(现在在用的)。反正什么python脚本都能往上加,就把它当个输出,如果需要输入还可以加gpio按键,不过程序设计不好加gpio流程会很复杂很复杂,现在已经不用了。然后还有rgb背光,根据一天中的时间自动变亮&变暗,每天主题色不同。


索尼tabletz,从2013年初中毕业用到现在大学毕业,很喜欢这平板,虽然硬件几年过时了,但是索尼优秀的工业设计是不会过时的。索尼现在砍掉了tablet项目,最后的tabletz4也挺产了,这平板可能还能撑一两年(去名古屋的时候它还是我手机坏掉以后的主力机呢)。它坏掉以后换什么呢?还没想法


它现在的主要功能hhhh,每天放一放abema,早上看下新闻,晚上可以看下声優と夜遊び,可惜舍友在的时候一般不好意思开外放,不然就可以每天早上听听新闻。日本的新闻没有央视那样的导向性,更像是东方卫视的新闻坊那样,比较娱乐,因为脱离欧美圈,一些问题也不用站政治正确立场,报道比较客观。反正我一般国内日媒bbc和dw都会看一看,不然看的不全面,容易产生偏见、先入为主。


最近发现的新功能,vban banana真的是个特nb的软件,电脑电脑之间音频串流,电脑串流到手机,然后用这个平板做外放音响,解决了台式机没音响的问题


防脱套装,能不能防脱不知道,不过用起来味道很好闻,洗完澡头发散发着洗发水的味道,大概就是这种感觉


淘宝的便宜笔盒,上面的时雨和扶桑山城是大一时候画的,(没错我以前是画画的),(naoto的画临摹),画了几年,从纸上到板绘,从临摹到创作,我觉得我没有领悟到绘画的真谛…那种去创作自己想传作的东西的感觉,去画自己的idea的感觉,一直都在遵守各种“方法”。其实没有必要,就应该凭着自己的感觉去画,不纠结于一个小块到底有没有“崩坏”,人物骨骼符不符合“标准”,而是就是把它作为表达想法表达情感的方式,让看的人能从中感受到你的心情。具体一点说,颜色很重要,我都没怎么画过厚涂,什么都是线稿然后一块一块上色,这是不对的,有很大局限的,应该上手直接涂,用颜色表达自己的心情,表达自己的想法,然后再去扣细节。。
说的我都想画了,觉得自己没天分停笔一两年了。。。


以前的画大概长这样


耳机,在寝室都用头戴,最早是老铁es700,现在丢在家里备用;之后用了半年同学的shp9500,还给同学之后换了msr7nc,虽然有降噪但是效果小的可怜;换了手机之后没了耳机孔,纠结半年正好实习有钱,换了索尼的xm3,现在的daily driver,买了两周降噪就坏了,有底噪,忍了一个月,送修,保修一周修好了,现在使用正常


高中的时候次次cp都会去,约上同学,在紧张学习之后休息一天,大学之后也去,不过因为约不到人去的少了。。。漫展还是不如同人展有意思,国内同人最近发展也是迅猛,然后都是女性向的(X

就算这样还是可以去拍coser,prpr

无聊、流水账式的叙述暂时到此为止吧,说是desk review但是桌上的东西都是回忆啊,不如说是大学回忆吧

用一些以前桌面的照片作为结束吧!


刚上大学


大一上


大一下


网速显示


刚拿到edison时,写好rgb,后面就是10块包邮的牧马人


大一困扰我的苏菲花屏 去ms论坛反映了好多次,全球sb用户都有这问题


“电竞模式”—— 听说屏幕越近,键盘约斜就会越强(是真的)

另一个屏幕还在玩gal233333


现在的样子

桌面承载着回忆,希望未来能变得更好


漏了这货,暑假里做的,基友的烂尾品,拿来做完涂色,用了郡士的硝基漆,效果挺好的(就是我涂厚了),船是和高雄合体之后的伊欧娜。基友不来拿,就在这放着

]]>
https://azusebox.moe/2019/11/06/%e3%80%90desk-review%e3%80%91%e6%a1%8c%e9%9d%a2%e8%af%84%e6%b5%8b/feed/ 2 12513
numpy常用数组操作 https://azusebox.moe/2019/10/30/numpy%e5%b8%b8%e7%94%a8%e6%95%b0%e7%bb%84%e6%93%8d%e4%bd%9c/ https://azusebox.moe/2019/10/30/numpy%e5%b8%b8%e7%94%a8%e6%95%b0%e7%bb%84%e6%93%8d%e4%bd%9c/#respond Tue, 29 Oct 2019 17:52:27 +0000 http://azusebox.moe/?p=12509

  • 矩阵转置
    \( A^T \) = A.T
  • 把(n,)矩阵转换为(n,1)矩阵
    A.reshape(-1, 1)

  • 数列求和

$$ \sum_{i=1}^{10} Z_i $$
$$ Z_i 又是三元数组(Z_i.shape = (3,)) $$
$$ 所以Z.shape = (3,10) $$
$$ 对Z_i求和: np.sum(Z, axis=1) $$

  • numpy矩阵x乘(行乘以列 )
    直接用*np.dot()(推荐,因为*时常需要先转换类型)
  • numpy矩阵*乘(元素与元素对应相乘 element-wise multiply)
    np.multiply()

注:
* Markdown 引入数学公式支持
<script type="text/javascript"
src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">

* Markdown 行内公式
\\( 公式 \\)

]]>
https://azusebox.moe/2019/10/30/numpy%e5%b8%b8%e7%94%a8%e6%95%b0%e7%bb%84%e6%93%8d%e4%bd%9c/feed/ 0 12509
【转】设计与算法 | Google Photos Web UI https://azusebox.moe/2019/09/24/%e3%80%90%e8%bd%ac%e3%80%91%e8%ae%be%e8%ae%a1%e4%b8%8e%e7%ae%97%e6%b3%95-google-photos-web-ui/ https://azusebox.moe/2019/09/24/%e3%80%90%e8%bd%ac%e3%80%91%e8%ae%be%e8%ae%a1%e4%b8%8e%e7%ae%97%e6%b3%95-google-photos-web-ui/#respond Tue, 24 Sep 2019 04:34:05 +0000 http://azusebox.moe/?p=12463

]]>
https://azusebox.moe/2019/09/24/%e3%80%90%e8%bd%ac%e3%80%91%e8%ae%be%e8%ae%a1%e4%b8%8e%e7%ae%97%e6%b3%95-google-photos-web-ui/feed/ 0 12463