работает слейв и мастер

This commit is contained in:
2026-05-28 03:20:13 +03:00
commit cbb571f124
191 changed files with 55316 additions and 0 deletions

50
App/app_config.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
#include "app_types.h"
/*
* Markdown note:
* - APP_ROLE_DEFAULT chooses firmware behavior at reset.
* - Role can be changed at runtime by App_SetRole().
* - Replace pin/channel values after CubeMX pinout is fixed.
*/
#define APP_ROLE_DEFAULT APP_ROLE_MASTER // APP_ROLE_MASTER //APP_ROLE_SLAVE
#define APP_ZIGBEE_ENDPOINT 10U
#define APP_ZIGBEE_CLUSTER_INPUTS 0xFC10U
#define APP_ZIGBEE_REPORT_PERIOD_MS 100U
#define APP_ZIGBEE_CHANNEL 20U
#define APP_ZIGBEE_PAN_ID 0x1234U
#define APP_ZIGBEE_EXTENDED_PAN_ID 0x1122334455667788ULL
#define APP_ZIGBEE_PERMIT_JOIN_SEC 0xFFU
#define APP_ZIGBEE_PERMIT_REFRESH_MS 5000U
#define APP_ZIGBEE_REJOIN_FAIL_COUNT 3U
#define APP_ZIGBEE_REJOIN_RETRY_MS 15000U
#define APP_ZIGBEE_JOIN_WATCHDOG_MS 20000U
#define APP_BUTTON_COUNT 3U
#define APP_ADC_CHANNEL_COUNT 1U
#define APP_BUTTON_ACTIVE_LEVEL 0U
#define APP_BUTTON_DEBOUNCE_MS 20U
#define APP_BUTTON_GPIO_PORT GPIOB
#define APP_BUTTON1_GPIO_PIN GPIO_PIN_0
#define APP_BUTTON2_GPIO_PIN GPIO_PIN_1
#define APP_BUTTON3_GPIO_PIN GPIO_PIN_2
#define APP_ROLE_DETECT_ENABLED 0U
#define APP_ROLE_DETECT_GPIO_PORT GPIOA
#define APP_ROLE_DETECT_GPIO_PIN GPIO_PIN_9
#define APP_ROLE_DETECT_PULL GPIO_PULLDOWN
#define APP_ROLE_DETECT_MASTER_LEVEL GPIO_PIN_RESET
#define APP_ROLE_LED_GPIO_PORT GPIOE
#define APP_ROLE_LED_GPIO_PIN GPIO_PIN_4
#define APP_ROLE_LED_ACTIVE_LEVEL GPIO_PIN_SET
#define APP_ANALOG_MIN_RAW 0U
#define APP_ANALOG_MAX_RAW 4095U
#endif

150
App/app_globals.c Normal file
View File

@@ -0,0 +1,150 @@
#include "app_config.h"
#include "app_types.h"
AppContext_t g_app =
{
.role = APP_ROLE_DEFAULT,
.zigbee_state = APP_ZB_DISCONNECTED,
.slave_inputs =
{
.buttons =
{
{ .pressed = false, .changed = false, .last_change_ms = 0U },
{ .pressed = false, .changed = false, .last_change_ms = 0U },
{ .pressed = false, .changed = false, .last_change_ms = 0U }
},
.analog = { .raw = 0U, .filtered = 0U, .percent = 0U },
.sequence = 0U
},
.master_node =
{
.online = false,
.last_report = { .sequence = 0U, .button_mask = 0U, .analog_raw = 0U, .analog_percent = 0U },
.last_seen_ms = 0U
},
.zigbee =
{
.pan_id = APP_ZIGBEE_PAN_ID,
.channel = APP_ZIGBEE_CHANNEL,
.endpoint = APP_ZIGBEE_ENDPOINT,
.cluster_id = APP_ZIGBEE_CLUSTER_INPUTS
},
.uptime_ms = 0U,
.last_report_ms = 0U
};
volatile uint32_t watch_role = APP_ROLE_DEFAULT;
volatile uint32_t watch_is_master = 0U;
volatile uint32_t watch_is_slave = 1U;
volatile uint32_t watch_role_pin_is_master = 0U;
volatile uint32_t watch_zigbee_state = APP_ZB_DISCONNECTED;
volatile uint32_t watch_master_online = 0U;
volatile uint32_t watch_master_last_seen_ms = 0U;
volatile uint32_t watch_master_last_sequence = 0U;
volatile uint32_t watch_master_button_mask = 0U;
volatile uint32_t watch_master_analog_raw = 0U;
volatile uint32_t watch_master_analog_percent = 0U;
volatile uint32_t watch_slave_sequence = 0U;
volatile uint32_t watch_slave_button_mask = 0U;
volatile uint32_t watch_slave_analog_raw = 0U;
volatile uint32_t watch_slave_analog_percent = 0U;
volatile uint32_t watch_report_tx_count = 0U;
volatile uint32_t watch_report_rx_count = 0U;
volatile uint32_t watch_report_tx_attempt_count = 0U;
volatile uint32_t watch_report_tx_ok_count = 0U;
volatile uint32_t watch_report_tx_busy_count = 0U;
volatile uint32_t watch_report_tx_confirm_count = 0U;
volatile uint32_t watch_report_tx_confirm_status = 0U;
volatile uint32_t watch_report_rx_ind_count = 0U;
volatile uint32_t watch_report_rx_decode_fail_count = 0U;
volatile uint32_t watch_report_rx_last_cluster = 0U;
volatile uint32_t watch_report_rx_last_length = 0U;
volatile uint32_t watch_zigbee_ready = 0U;
volatile uint32_t watch_rejoin_request_count = 0U;
volatile uint32_t watch_rejoin_success_count = 0U;
volatile uint32_t watch_rejoin_fail_count = 0U;
volatile uint32_t watch_rejoin_active = 0U;
volatile uint32_t watch_rejoin_last_status = 0U;
volatile uint32_t watch_report_tx_fail_streak = 0U;
volatile uint32_t watch_permit_join_count = 0U;
volatile uint32_t watch_permit_join_status = 0U;
volatile uint32_t watch_permit_join_duration = 0U;
volatile uint32_t watch_join_watchdog_count = 0U;
static void App_DebugCopyReportToMasterWatch(const AppSlaveReport_t *report)
{
watch_master_last_sequence = report->sequence;
watch_master_button_mask = report->button_mask;
watch_master_analog_raw = report->analog_raw;
watch_master_analog_percent = report->analog_percent;
}
static void App_DebugCopyReportToSlaveWatch(const AppSlaveReport_t *report)
{
watch_slave_sequence = report->sequence;
watch_slave_button_mask = report->button_mask;
watch_slave_analog_raw = report->analog_raw;
watch_slave_analog_percent = report->analog_percent;
}
const char *App_RoleName(AppRole_t role)
{
return (role == APP_ROLE_MASTER) ? "master" : "slave";
}
void App_SetRole(AppRole_t role)
{
g_app.role = role;
g_app.zigbee_state = APP_ZB_DISCONNECTED;
g_app.last_report_ms = 0U;
}
AppSlaveReport_t App_MakeSlaveReport(const AppSlaveInputs_t *inputs)
{
AppSlaveReport_t report;
uint8_t mask = 0U;
for (uint8_t i = 0U; i < APP_BUTTON_COUNT; i++)
{
if (inputs->buttons[i].pressed)
{
mask |= (uint8_t)(1U << i);
}
}
report.sequence = inputs->sequence;
report.button_mask = mask;
report.analog_raw = inputs->analog.raw;
report.analog_percent = inputs->analog.percent;
return report;
}
void App_MasterAcceptReport(const AppSlaveReport_t *report, uint32_t now_ms)
{
g_app.master_node.online = true;
g_app.master_node.last_report = *report;
g_app.master_node.last_seen_ms = now_ms;
watch_report_rx_count++;
App_DebugCopyReportToMasterWatch(report);
App_DebugRefresh(watch_role_pin_is_master);
}
void App_DebugRefresh(uint32_t role_pin_is_master)
{
watch_role = (uint32_t)g_app.role;
watch_is_master = (g_app.role == APP_ROLE_MASTER) ? 1U : 0U;
watch_is_slave = (g_app.role == APP_ROLE_SLAVE) ? 1U : 0U;
watch_role_pin_is_master = role_pin_is_master;
watch_zigbee_state = (uint32_t)g_app.zigbee_state;
watch_master_online = g_app.master_node.online ? 1U : 0U;
watch_master_last_seen_ms = g_app.master_node.last_seen_ms;
App_DebugCopyReportToMasterWatch(&g_app.master_node.last_report);
}
void App_DebugOnSlaveReportTx(const AppSlaveReport_t *report)
{
watch_report_tx_count++;
App_DebugCopyReportToSlaveWatch(report);
}

123
App/app_types.h Normal file
View File

@@ -0,0 +1,123 @@
#ifndef APP_TYPES_H
#define APP_TYPES_H
#include <stdbool.h>
#include <stdint.h>
typedef enum
{
APP_ROLE_MASTER = 1,
APP_ROLE_SLAVE = 0
} AppRole_t;
typedef enum
{
APP_ZB_DISCONNECTED = 0,
APP_ZB_JOINING,
APP_ZB_CONNECTED,
APP_ZB_ERROR
} AppZigbeeState_t;
typedef struct
{
bool pressed;
bool changed;
uint32_t last_change_ms;
} AppButton_t;
typedef struct
{
uint16_t raw;
uint16_t filtered;
uint8_t percent;
} AppAnalogChannel_t;
typedef struct
{
AppButton_t buttons[3];
AppAnalogChannel_t analog;
uint32_t sequence;
} AppSlaveInputs_t;
typedef struct
{
uint32_t sequence;
uint8_t button_mask;
uint16_t analog_raw;
uint8_t analog_percent;
} AppSlaveReport_t;
typedef struct
{
bool online;
AppSlaveReport_t last_report;
uint32_t last_seen_ms;
} AppMasterNode_t;
typedef struct
{
uint16_t pan_id;
uint8_t channel;
uint8_t endpoint;
uint16_t cluster_id;
} AppZigbeeConfig_t;
typedef struct
{
AppRole_t role;
AppZigbeeState_t zigbee_state;
AppSlaveInputs_t slave_inputs;
AppMasterNode_t master_node;
AppZigbeeConfig_t zigbee;
uint32_t uptime_ms;
uint32_t last_report_ms;
} AppContext_t;
extern AppContext_t g_app;
extern volatile uint32_t watch_role;
extern volatile uint32_t watch_is_master;
extern volatile uint32_t watch_is_slave;
extern volatile uint32_t watch_role_pin_is_master;
extern volatile uint32_t watch_zigbee_state;
extern volatile uint32_t watch_master_online;
extern volatile uint32_t watch_master_last_seen_ms;
extern volatile uint32_t watch_master_last_sequence;
extern volatile uint32_t watch_master_button_mask;
extern volatile uint32_t watch_master_analog_raw;
extern volatile uint32_t watch_master_analog_percent;
extern volatile uint32_t watch_slave_sequence;
extern volatile uint32_t watch_slave_button_mask;
extern volatile uint32_t watch_slave_analog_raw;
extern volatile uint32_t watch_slave_analog_percent;
extern volatile uint32_t watch_report_tx_count;
extern volatile uint32_t watch_report_rx_count;
extern volatile uint32_t watch_report_tx_attempt_count;
extern volatile uint32_t watch_report_tx_ok_count;
extern volatile uint32_t watch_report_tx_busy_count;
extern volatile uint32_t watch_report_tx_confirm_count;
extern volatile uint32_t watch_report_tx_confirm_status;
extern volatile uint32_t watch_report_rx_ind_count;
extern volatile uint32_t watch_report_rx_decode_fail_count;
extern volatile uint32_t watch_report_rx_last_cluster;
extern volatile uint32_t watch_report_rx_last_length;
extern volatile uint32_t watch_zigbee_ready;
extern volatile uint32_t watch_rejoin_request_count;
extern volatile uint32_t watch_rejoin_success_count;
extern volatile uint32_t watch_rejoin_fail_count;
extern volatile uint32_t watch_rejoin_active;
extern volatile uint32_t watch_rejoin_last_status;
extern volatile uint32_t watch_report_tx_fail_streak;
extern volatile uint32_t watch_permit_join_count;
extern volatile uint32_t watch_permit_join_status;
extern volatile uint32_t watch_permit_join_duration;
extern volatile uint32_t watch_join_watchdog_count;
const char *App_RoleName(AppRole_t role);
void App_SetRole(AppRole_t role);
AppSlaveReport_t App_MakeSlaveReport(const AppSlaveInputs_t *inputs);
void App_MasterAcceptReport(const AppSlaveReport_t *report, uint32_t now_ms);
void App_DebugRefresh(uint32_t role_pin_is_master);
void App_DebugOnSlaveReportTx(const AppSlaveReport_t *report);
#endif

100
App/hardware.c Normal file
View File

@@ -0,0 +1,100 @@
#include "hardware.h"
#include "app_config.h"
#include "main.h"
#include "stm32wbxx_nucleo.h"
/*
* Markdown note:
* - This file is the only place that should know real GPIO/ADC pins.
* - Replace the weak placeholder code with HAL_GPIO_ReadPin() and HAL_ADC calls
* after STM32CubeWB/CubeMX generates the board initialization.
*/
void Hardware_Init(void)
{
GPIO_InitTypeDef gpio = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
#if (APP_ROLE_DETECT_ENABLED != 0U)
gpio.Pin = APP_ROLE_DETECT_GPIO_PIN;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = APP_ROLE_DETECT_PULL;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(APP_ROLE_DETECT_GPIO_PORT, &gpio);
#endif
gpio.Pin = APP_BUTTON1_GPIO_PIN | APP_BUTTON2_GPIO_PIN | APP_BUTTON3_GPIO_PIN;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = (APP_BUTTON_ACTIVE_LEVEL == 0U) ? GPIO_PULLUP : GPIO_PULLDOWN;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(APP_BUTTON_GPIO_PORT, &gpio);
gpio.Pin = APP_ROLE_LED_GPIO_PIN;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(APP_ROLE_LED_GPIO_PORT, &gpio);
Hardware_SetRoleLed(false);
}
void Hardware_Process(void)
{
}
uint32_t Hardware_GetTickMs(void)
{
return HAL_GetTick();
}
bool Hardware_ReadRoleSwitchIsMaster(void)
{
#if (APP_ROLE_DETECT_ENABLED != 0U)
return (HAL_GPIO_ReadPin(APP_ROLE_DETECT_GPIO_PORT, APP_ROLE_DETECT_GPIO_PIN) ==
APP_ROLE_DETECT_MASTER_LEVEL);
#else
return (APP_ROLE_DEFAULT == APP_ROLE_MASTER);
#endif
}
void Hardware_SetRoleLed(bool on)
{
const GPIO_PinState active = APP_ROLE_LED_ACTIVE_LEVEL;
const GPIO_PinState inactive = (active == GPIO_PIN_SET) ? GPIO_PIN_RESET : GPIO_PIN_SET;
HAL_GPIO_WritePin(APP_ROLE_LED_GPIO_PORT,
APP_ROLE_LED_GPIO_PIN,
on ? active : inactive);
}
bool Hardware_ReadButton(uint8_t index)
{
uint16_t pin;
switch (index)
{
case 0U:
pin = APP_BUTTON1_GPIO_PIN;
break;
case 1U:
pin = APP_BUTTON2_GPIO_PIN;
break;
case 2U:
pin = APP_BUTTON3_GPIO_PIN;
break;
default:
return false;
}
return (HAL_GPIO_ReadPin(APP_BUTTON_GPIO_PORT, pin) == APP_BUTTON_ACTIVE_LEVEL);
}
uint16_t Hardware_ReadAnalogRaw(void)
{
return 0U;
}

15
App/hardware.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef HARDWARE_H
#define HARDWARE_H
#include <stdbool.h>
#include <stdint.h>
void Hardware_Init(void);
void Hardware_Process(void);
uint32_t Hardware_GetTickMs(void);
bool Hardware_ReadRoleSwitchIsMaster(void);
void Hardware_SetRoleLed(bool on);
bool Hardware_ReadButton(uint8_t index);
uint16_t Hardware_ReadAnalogRaw(void);
#endif

84
App/slave_inputs.c Normal file
View File

@@ -0,0 +1,84 @@
#include "app_config.h"
#include "app_types.h"
#include "hardware.h"
#include "slave_inputs.h"
static uint8_t Analog_ToPercent(uint16_t raw)
{
uint32_t clamped = raw;
if (clamped > APP_ANALOG_MAX_RAW)
{
clamped = APP_ANALOG_MAX_RAW;
}
return (uint8_t)((clamped * 100U) / APP_ANALOG_MAX_RAW);
}
void SlaveInputs_Init(void)
{
for (uint8_t i = 0U; i < APP_BUTTON_COUNT; i++)
{
g_app.slave_inputs.buttons[i].pressed = Hardware_ReadButton(i);
g_app.slave_inputs.buttons[i].changed = false;
g_app.slave_inputs.buttons[i].last_change_ms = Hardware_GetTickMs();
}
g_app.slave_inputs.analog.raw = Hardware_ReadAnalogRaw();
g_app.slave_inputs.analog.filtered = g_app.slave_inputs.analog.raw;
g_app.slave_inputs.analog.percent = Analog_ToPercent(g_app.slave_inputs.analog.filtered);
}
void SlaveInputs_Process(void)
{
const uint32_t now = Hardware_GetTickMs();
for (uint8_t i = 0U; i < APP_BUTTON_COUNT; i++)
{
const bool pressed = Hardware_ReadButton(i);
AppButton_t *button = &g_app.slave_inputs.buttons[i];
if ((pressed != button->pressed) &&
((now - button->last_change_ms) >= APP_BUTTON_DEBOUNCE_MS))
{
button->pressed = pressed;
button->changed = true;
button->last_change_ms = now;
g_app.slave_inputs.sequence++;
}
}
const uint16_t raw = Hardware_ReadAnalogRaw();
AppAnalogChannel_t *analog = &g_app.slave_inputs.analog;
const uint16_t old_percent = analog->percent;
analog->raw = raw;
analog->filtered = (uint16_t)(((uint32_t)analog->filtered * 7U + raw) / 8U);
analog->percent = Analog_ToPercent(analog->filtered);
if (analog->percent != old_percent)
{
g_app.slave_inputs.sequence++;
}
}
int SlaveInputs_HasChanges(void)
{
for (uint8_t i = 0U; i < APP_BUTTON_COUNT; i++)
{
if (g_app.slave_inputs.buttons[i].changed)
{
return 1;
}
}
return 0;
}
void SlaveInputs_ClearChanges(void)
{
for (uint8_t i = 0U; i < APP_BUTTON_COUNT; i++)
{
g_app.slave_inputs.buttons[i].changed = false;
}
}

9
App/slave_inputs.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef SLAVE_INPUTS_H
#define SLAVE_INPUTS_H
void SlaveInputs_Init(void);
void SlaveInputs_Process(void);
int SlaveInputs_HasChanges(void);
void SlaveInputs_ClearChanges(void);
#endif

89
App/zigbee_app.c Normal file
View File

@@ -0,0 +1,89 @@
#include "app_config.h"
#include "app_types.h"
#include "hardware.h"
#include "slave_inputs.h"
#include "zigbee_app.h"
#include "zigbee_port.h"
static void ZigbeeApp_UpdateStatusLed(void)
{
const uint32_t phase = g_app.uptime_ms % 1000U;
bool on = false;
switch (g_app.zigbee_state)
{
case APP_ZB_CONNECTED:
if (g_app.role == APP_ROLE_MASTER)
{
on = (phase < 80U);
}
else
{
on = (phase < 80U) || ((phase >= 160U) && (phase < 240U));
}
break;
case APP_ZB_JOINING:
on = ((phase % 160U) < 80U);
break;
case APP_ZB_ERROR:
on = ((phase % 100U) < 50U);
break;
case APP_ZB_DISCONNECTED:
default:
on = ((phase % 400U) < 80U);
break;
}
Hardware_SetRoleLed(on);
}
static void ZigbeeApp_UpdateRoleFromSwitch(void)
{
const bool role_pin_is_master = Hardware_ReadRoleSwitchIsMaster();
const AppRole_t requested_role = role_pin_is_master;
// role_pin_is_master ? APP_ROLE_MASTER : APP_ROLE_SLAVE;
if (requested_role != g_app.role)
{
App_SetRole(requested_role);
ZigbeePort_Init(&g_app.zigbee, g_app.role);
}
App_DebugRefresh(role_pin_is_master ? 1U : 0U);
}
void ZigbeeApp_Init(void)
{
ZigbeePort_Init(&g_app.zigbee, g_app.role);
SlaveInputs_Init();
const bool role_pin_is_master = Hardware_ReadRoleSwitchIsMaster();
App_DebugRefresh(role_pin_is_master ? 1U : 0U);
ZigbeeApp_UpdateStatusLed();
}
void ZigbeeApp_Process(void)
{
g_app.uptime_ms = Hardware_GetTickMs();
ZigbeeApp_UpdateRoleFromSwitch();
ZigbeePort_Process();
ZigbeeApp_UpdateStatusLed();
if (g_app.role == APP_ROLE_SLAVE)
{
SlaveInputs_Process();
if ((g_app.uptime_ms - g_app.last_report_ms) >= APP_ZIGBEE_REPORT_PERIOD_MS)
{
if (ZigbeePort_SendSlaveInputs(&g_app.slave_inputs) == ZIGBEE_PORT_OK)
{
g_app.last_report_ms = g_app.uptime_ms;
SlaveInputs_ClearChanges();
}
}
}
}

7
App/zigbee_app.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef ZIGBEE_APP_H
#define ZIGBEE_APP_H
void ZigbeeApp_Init(void);
void ZigbeeApp_Process(void);
#endif

34
App/zigbee_port.c Normal file
View File

@@ -0,0 +1,34 @@
#include "zigbee_port.h"
#include "app_zigbee.h"
/*
* Markdown note:
* - Keep STM32_WPAN / Zigbee Cluster Library calls in this adapter.
* - Master should create/open the network and receive reports.
* - Slave should join the network and send AppSlaveInputs_t payload.
*/
void ZigbeePort_Init(const AppZigbeeConfig_t *config, AppRole_t role)
{
(void)config;
(void)role;
}
void ZigbeePort_Process(void)
{
APP_ZIGBEE_Process();
}
ZigbeePortStatus_t ZigbeePort_SendSlaveInputs(const AppSlaveInputs_t *inputs)
{
const AppSlaveReport_t report = App_MakeSlaveReport(inputs);
App_DebugOnSlaveReportTx(&report);
return APP_ZIGBEE_SendSlaveReport(&report) ? ZIGBEE_PORT_OK : ZIGBEE_PORT_BUSY;
}
void ZigbeePort_OnSlaveReportReceived(const AppSlaveReport_t *report, uint32_t now_ms)
{
App_MasterAcceptReport(report, now_ms);
}

19
App/zigbee_port.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef ZIGBEE_PORT_H
#define ZIGBEE_PORT_H
#include <stdint.h>
#include "app_types.h"
typedef enum
{
ZIGBEE_PORT_OK = 0,
ZIGBEE_PORT_BUSY,
ZIGBEE_PORT_ERROR
} ZigbeePortStatus_t;
void ZigbeePort_Init(const AppZigbeeConfig_t *config, AppRole_t role);
void ZigbeePort_Process(void);
ZigbeePortStatus_t ZigbeePort_SendSlaveInputs(const AppSlaveInputs_t *inputs);
void ZigbeePort_OnSlaveReportReceived(const AppSlaveReport_t *report, uint32_t now_ms);
#endif