摩斯密码(Morse Code)是一种通过点(.)和划(-)组合来表示字符的编码系统。下面我将在esp32上实现摩斯密码的输入,并能够发送到mqtt的broker。
先捋一下逻辑,首先esp32的按键已经编写了短按与长按功能,这将是输出摩斯密码点和划的基础。然后当2s没有新输入,我们就认为输入完了一个字符,自动识别即可。但是怎么发送到mqtt呢?我打算选用一种摩斯密码里没有的,并且又容易记忆的字符——6个点。
于是逻辑图如下:有了逻辑,编写代码就很容易了。
先编写一个密码表:
// 摩尔斯码表 - 常见字符
static const struct
{
char character;
const char *morse;
} morse_table[] = {
{'A', ".-"},
{'B', "-..."},
{'C', "-.-."},
{'D', "-.."},
{'E', "."},
{'F', "..-."},
{'G', "--."},
{'H', "...."},
{'I', ".."},
{'J', ".---"},
{'K', "-.-"},
{'L', ".-.."},
{'M', "--"},
{'N', "-."},
{'O', "---"},
{'P', ".--."},
{'Q', "--.-"},
{'R', ".-."},
{'S', "..."},
{'T', "-"},
{'U', "..-"},
{'V', "...-"},
{'W', ".--"},
{'X', "-..-"},
{'Y', "-.--"},
{'Z', "--.."},
{'0', "-----"},
{'1', ".----"},
{'2', "..---"},
{'3', "...--"},
{'4', "....-"},
{'5', "....."},
{'6', "-...."},
{'7', "--..."},
{'8', "---.."},
{'9', "----."},
{' ', "/"}, // 空格用"/"表示
{'\0', NULL} // 结束标记
};
定义摩斯密码的输入状态与输入类型。
// 摩尔斯码输入状态
typedef enum
{
MORSE_IDLE, // 空闲状态
MORSE_INPUT, // 输入中
MORSE_CHAR_COMPLETE // 字符输入完成
} morse_state_t;
// 摩尔斯码输入类型
typedef enum
{
MORSE_DOT, // 点 (.)
MORSE_DASH, // 划 (-)
MORSE_GAP // 间隔 (表示字符结束)
} morse_input_t;
编写宏定义与变量
#define MORSE_CHAR_TIMEOUT 2000 // 字符超时时间(毫秒)
#define MORSE_MAX_BUFFER 32 // 最大缓冲区大小。限制当前正在输入的摩尔斯码序列(点和划)的最大长度
#define MORSE_OUTPUT_MAX 64 // 输出缓冲区最大大小。限制已解码文本的最大长度
// 当前摩尔斯码输入
static morse_state_t morse_state = MORSE_IDLE;//跟踪摩尔斯码输入的当前状态
static char current_morse[MORSE_MAX_BUFFER] = {0};//存储当前正在输入的摩尔斯码序列
static char decoded_text[MORSE_OUTPUT_MAX] = {0};//存储已解码的完整文本
static int64_t last_input_time = 0;//记录最后一次用户输入的时间戳(毫秒)。用于检测输入超时,实现自动字符完成功能。通过与当前时间比较,判断是否超过了MORSE_CHAR_TIMEOUT
相应的编写各种功能函数:
// 获取当前时间戳(毫秒)
static int64_t get_current_time_ms(void)
{
return esp_timer_get_time() / 1000;
}
// 初始化摩尔斯码模块
void morse_init(void)
{
morse_reset();
}
// 添加一个摩尔斯码符号
void morse_add_symbol(morse_input_t symbol)
{
size_t len = strlen(current_morse);
// 防止缓冲区溢出
if (len >= MORSE_MAX_BUFFER - 2)
{
return;
}
// 添加符号
switch (symbol)
{
case MORSE_DOT:
current_morse[len] = '.';
current_morse[len + 1] = '\0';
break;
case MORSE_DASH:
current_morse[len] = '-';
current_morse[len + 1] = '\0';
break;
case MORSE_GAP:
// 解码当前字符
char decoded = morse_decode_current();
if (decoded != '\0')
{
size_t decoded_len = strlen(decoded_text);
if (decoded_len < MORSE_OUTPUT_MAX - 1)
{
decoded_text[decoded_len] = decoded;
decoded_text[decoded_len + 1] = '\0';
}
}
// 重置当前输入
current_morse[0] = '\0';
break;
}
// 更新状态和时间戳
morse_state = (symbol == MORSE_GAP) ? MORSE_CHAR_COMPLETE : MORSE_INPUT;
last_input_time = get_current_time_ms();
}
// 检查是否需要完成当前字符
bool morse_check_timeout(int64_t current_time)
{
// 如果有输入且超时,则完成当前字符
if (morse_state == MORSE_INPUT &&
strlen(current_morse) > 0 &&
(current_time - last_input_time) > MORSE_CHAR_TIMEOUT)
{
morse_add_symbol(MORSE_GAP);
return true;
}
return false;
}
// 解码当前的摩尔斯码
char morse_decode_current(void)
{
if (strlen(current_morse) == 0)
{
return '\0';
}
// 尝试在摩尔斯码表中查找
for (int i = 0; morse_table[i].morse != NULL; i++)
{
if (strcmp(current_morse, morse_table[i].morse) == 0)
{
return morse_table[i].character;
}
}
// 如果找不到匹配项
return '?';
}
// 重置摩尔斯码输入
void morse_reset(void)
{
morse_state = MORSE_IDLE;
current_morse[0] = '\0';
decoded_text[0] = '\0';
last_input_time = get_current_time_ms();
}
// 获取当前摩尔斯码字符串
const char *morse_get_current(void)
{
return current_morse;
}
// 获取已解码的字符串
const char *morse_get_decoded(void)
{
return decoded_text;
}
// 为显示准备摩尔斯码和解码结果
void morse_prepare_display(char *buffer, int size)
{
// 拼接解码结果和当前输入的摩尔斯码
snprintf(buffer, size, "解码: %s\n当前: %s",
strlen(decoded_text) > 0 ? decoded_text : "[空]",
strlen(current_morse) > 0 ? current_morse : "[等待输入]");
}
// 检查当前输入是否为6个连续的点
bool morse_is_six_dots(void)
{
// 检查当前输入是否为"......"(6个点)
if (strlen(current_morse) == 6)
{
// 逐个检查是否都是点
for (int i = 0; i < 6; i++)
{
if (current_morse[i] != '.')
{
return false;
}
}
return true;
}
return false;
}
在主函数调用先调用初始化。
// 初始化摩尔斯码模块
morse_init();
在while循环里面,首先加入超时处理机制,2s没有再输入便直接解码,并在屏幕上显示。
// 获取当前时间
int64_t current_time = esp_timer_get_time() / 1000;
// 检查摩尔斯码输入超时
if (current_mode == MODE_MORSE)
{
if (morse_check_timeout(current_time))
{
// 超时处理,更新显示
char morse_display[128];
morse_prepare_display(morse_display, sizeof(morse_display));
clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);
draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, morse_display, 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);
}
}
在按键短按的触发函数里面,按照前面的逻辑框图写带代码:先加入一个点,在判断是不是特殊消息(6个点),是的话直接用mqtt发送目前解析好的字符串,并重置,在屏上显示消息。如果不是,则正常更新显示,等待下一个输入。
// 短按输入"点"(.)
morse_add_symbol(MORSE_DOT);
// 检查是否为连续6个点
if (morse_is_six_dots() && mqtt_connected)
{
// 发送解码后的摩尔斯码消息到MQTT
char message[50];
const char *decoded = morse_get_decoded();
// 如果有解码结果,发送它,否则发送当前的摩尔斯码符号
if (strlen(decoded) > 0)
{
sprintf(message, "Morse: %s", decoded);
}
else
{
sprintf(message, "Morse: ......");
}
esp_mqtt_client_publish(mqtt_client, MQTT_PUBLISH_TOPIC, message, 0, 1, 0);
// 重置摩尔斯码输入并显示发送成功信息
morse_reset();
clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);
draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, "信号已发送", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);
// 延迟1秒让用户看到发送成功消息
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
else
{
// 正常更新显示
char morse_display[128];
morse_prepare_display(morse_display, sizeof(morse_display));
clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);
draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, morse_display, 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);
}
长按直接添加划,并更新显示就好了。
// 长按输入"划"(-)
morse_add_symbol(MORSE_DASH);
// 更新显示
char morse_display[128];
morse_prepare_display(morse_display, sizeof(morse_display));
clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);
draw_string(lcd_buffer, 0, 0, "摩尔斯电码模式", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT + 2, morse_display, 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 4 + 8, "短按: 输入点(.)", 0xFFFF, LCD_H_RES);
draw_string(lcd_buffer, 0, CHINESE_FONT_HEIGHT * 5 + 10, "长按: 输入划(-)", 0xFFFF, LCD_H_RES);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);
break;
ok,代码写完了,让我们试试效果。
收到了。