Compare commits

..

14 Commits

Author SHA1 Message Date
286028b6a8 fix evse_link 2026-01-24 16:56:51 +00:00
023644a887 new upgrade 2025-12-21 23:28:26 +00:00
82fa194bd8 v1.1 2025-12-10 12:59:55 +00:00
e6e2622a95 new module 2025-12-09 11:48:31 +00:00
4820d9111e new release 2025-11-20 07:45:00 +00:00
96b2ab1f57 add ocpp 2025-08-24 11:17:48 +01:00
0d0dc5b129 evse_link feature 2025-08-05 16:55:11 +01:00
bd587a10c0 new buzzer component 2025-07-22 00:09:58 +01:00
84f106eee5 fix ade7758 2025-06-25 06:34:03 +01:00
a0b2e048d4 new meter 2025-06-14 11:46:10 +01:00
6f95c7ba59 new meter 2025-06-14 10:27:29 +01:00
4892718736 add net manager 2025-06-09 10:51:02 +01:00
66cc449143 add orno driver 2025-06-08 19:26:46 +01:00
12dfa85820 add orno driver 2025-06-08 18:35:32 +01:00
318 changed files with 21171 additions and 28881 deletions

View File

@@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32-evse)
project(chargeflow)

209
README.md
View File

@@ -1,85 +1,180 @@
![ESP32 EVSE](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/logo-full.svg)
# ChargeFlow EVSE Firmware (ESP32, ESP-IDF 5.x)
J1772 EVSE firmware for ESP32 based devices.
Firmware for an AC EVSE (EV charger) based on ESP32 and ESP-IDF 5.x, with:
![Build with ESP-IDF](https://github.com/dzurikmiroslav/esp32-evse/workflows/Build%20with%20ESP-IDF/badge.svg)
[![License](https://img.shields.io/github/license/dzurikmiroslav/esp32-evse.svg)](LICENSE.md)
- IEC-style EVSE state machine (Control Pilot A/B/C/D)
- Wi-Fi (STA + AP for local configuration)
- REST API served from SPIFFS
- Local authentication and OCPP integration
- Load balancing (master + slaves)
- Scheduler (time windows)
- Audible feedback (buzzer) and RGB LED status
- On-device ring-buffer logger
## Key features
- Hardware abstraction for device design
- Responsive web-interface
- OTA update
- Integrated energy meter
- Energy detection for relay control
- [REST](https://github.com/dzurikmiroslav/esp32-evse/wiki/Rest) API
- MQTT API
- [Modbus](https://github.com/dzurikmiroslav/esp32-evse/wiki/Modbus) (RS485, TCP)
- [Scripting](https://github.com/dzurikmiroslav/esp32-evse/wiki/Script)
- [Nextion HMI](https://github.com/dzurikmiroslav/esp32-evse/wiki/Nextion)
---
### Device definition method
## Features
_One firmware to rule them all._ Not really :-) one per device platform (ESP32, ESP32-S2...).
### Core EVSE
There is no need to compile the firmware for your EVSE design.
Source code ist not hardcoded to GPIOs or other hardware design features.
All code is written in ESP-IDF without additional mapping layer like Arduino.
- EVSE manager (`evse_manager`) coordinating:
- Hardware layer (`evse_hardware`)
- State machine (`evse_state`)
- Error handling (`evse_error`)
- Energy metering (`evse_meter` / `meter_manager`)
- Session tracking (`evse_session`)
- Runs a periodic tick (`evse_manager_tick()`) in its own FreeRTOS task.
- Supports multiple auth modes (OPEN / RFID / OCPP), with scheduling and load-balancer aware logic.
All configuration is written outside firmware in configuration file named _board.cfg_ on dedicated partition.
For example, on following scheme is minimal EVSE circuit with ESP32 devkit.
### Networking & REST
![Minimal circuit](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/minimal-circuit.png)
- Wi-Fi:
- Station mode for normal operation.
- Access point mode for local configuration, enabled by a physical button.
- REST server (`rest_main`) serving from `/data` SPIFFS mount:
- For configuration, status, logs, etc. (exact endpoints depend on your REST implementation).
For this circuit there is _board.cfg_, for more information's see [Wiki](https://github.com/dzurikmiroslav/esp32-evse/wiki/Board-config).
### Button & User Input
- One physical button (configured via `board_config`):
- **Short press** → Starts Wi-Fi AP mode for configuration.
- **Long press (~30s)** → Erases NVS and reboots (factory-like reset).
- Robust handling:
- ISR with software debounce and spinlock.
- Dedicated `user_input_task` that receives button press/release notifications via `xTaskNotify`.
```bash
#Device name
DEVICE_NAME=ESP32 minimal EVSE
#Button
BUTTON_WIFI_GPIO=0
#Pilot
PILOT_PWM_GPIO=33
PILOT_ADC_CHANNEL=7
PILOT_DOWN_THRESHOLD_12=2410
PILOT_DOWN_THRESHOLD_9=2104
PILOT_DOWN_THRESHOLD_6=1797
PILOT_DOWN_THRESHOLD_3=1491
PILOT_DOWN_THRESHOLD_N12=265
#AC relay
AC_RELAY_GPIO=32
```
### Storage
### Web interface
- SPIFFS used for:
- `/cfg` partition: persistent configuration.
- `/data` partition: web assets, runtime data, logs, etc.
- Two separate mounts:
- `cfg_conf``/cfg` (label: `cfg`)
- `data_conf``/data` (label: `data`)
Fully responsive web interface is accessible local network IP address on port 80.
### LED Subsystem
Dashboard page
- RGB LED driven by LEDC:
- `ledc_driver` abstracts LEDC timer + channels.
- `led` module maps EVSE state & sessions to colors/patterns.
- LED patterns per EVSE state:
- **IDLE** → Green solid.
- **WAITING** (vehicle plugged, not charging) → Blue slow blink.
- **CHARGING** → Blue “breathing” effect.
- **FAULT** → Red fast blink.
- Session effects:
- Distinct visual patterns when a session starts/finishes.
- Uses a one-shot timer and a dedicated effect state machine.
![Dashboard](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-dashboard.png)
### Buzzer
Settings page
- Buzzer with multiple patterns (`buzzer` + `buzzer_events`):
- Plugged/unplugged, card read/denied, AP start, charging, fault, etc.
- Supported modes:
- Active buzzer (ON/OFF).
- Passive buzzer with LEDC PWM (frequency & duty configurable).
- Features:
- Central queue + dedicated `buzzer_task`.
- Quiet hours support (optionally suppress non-critical sounds at night).
- Anti-spam mechanism to avoid excessively frequent beeps.
- Integrated with:
- EVSE events (state changes & faults)
- Auth events (RFID card success/denied/added)
- Network events (AP/STA up)
![Settings](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-settings.png)
### Load Balancer
Mobile dashboard page
- `loadbalancer` component:
- Monitors GRID meter and EVSE meter via `meter_events`.
- Supports one master + up to 255 slaves (connectors array).
- Fair distribution of current with:
- Headroom calculation based on grid limit and measured current.
- Min current guarantees (e.g. 6 A) using a “water-filling” algorithm.
- Session-age based priority (oldest sessions first).
- Per-connector hysteresis and LB suspension/resume flags.
- Publishes limits via `LOADBALANCER_EVENTS`:
- `LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT`
- `LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT`
- Fail-safe behavior:
- If GRID meter data times out, clamps connectors to minimum safe current instead of ramping up.
![Dashboard mobile](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/web-dashboard-mobile.png)
### Scheduler
## Hardware
- Scheduler component (`scheduler`) emits `SCHED_EVENTS` with `allowed_now` flag:
- EVSE manager revokes authorization when the window closes.
- In OPEN mode, automatic re-authorization only happens when scheduler allows.
### ESP32DevkitC
### OCPP
Dev board with basic functionality, single phase energy meter, RS485. One side pcb, for DIY makers easy to make at home conditions ;-)
- `ocpp` module integration:
- Listens to OCPP events (`OCPP_EVENTS`).
- Handles:
- RemoteStart/Stop
- Authorization results
- ChangeAvailability (operative/inoperative) → mapped into local `enabled` config.
- EVSE manager mediates OCPP decisions with scheduler + load balancer.
[EasyEDA project](https://oshwlab.com/dzurik.miroslav/esp32-devkit-evse)
### Logger
![ESP32DevkitC](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/esp32devkitc.jpg)
- `logger` + `output_buffer` components:
- Central log sink with ring buffer in RAM.
- Thread-safe via FreeRTOS mutex.
- Integrated with ESP log system via `esp_log_set_vprintf(logger_vprintf);`
- Optionally mirrors to UART (controlled via `CONFIG_ESP_CONSOLE_UART`).
- Simple reader API:
- Iterate entries using an index.
- Handy for exposing logs over REST/Web UI.
### ESP32-S2 DIY ALPHA
---
ESP32-S2 based EVSE with advanced functionality, three phase energy meter, RS485, UART, 1WIRE, RCM, socket lock.
## Project Structure (Relevant Parts)
[EasyEDA project](https://oshwlab.com/dzurik.miroslav/esp32s2-diy-evse)
Approximate layout (names may vary slightly in your repo):
![ESP32-S2-DA](https://github.com/dzurikmiroslav/esp32-evse/wiki/images/esp32s2da.jpg)
```text
main/
main.c # System entrypoint, button setup, module init
components/
evse/
evse_manager.c/.h # High-level EVSE orchestration
evse_state.c/.h # State machine & events
evse_error.c/.h # Error handling
evse_hardware.c/.h # Hardware abstraction
evse_session.c/.h # Session metrics
loadbalancer/
src/
loadbalancer.c
loadbalancer_events.c
input_filter.c
include/
loadbalancer.h
loadbalancer_events.h
input_filter.h
buzzer/
src/
buzzer.c
buzzer_events.c
include/
buzzer.h
buzzer_events.h
led/
src/
led.c
ledc_driver.c
include/
led.h
ledc_driver.h
logger/
src/
logger.c
output_buffer.c
include/
logger.h
output_buffer.h
# ... other modules: auth, ocpp, scheduler, meter_manager, evse_link, etc.

View File

@@ -1,12 +1,12 @@
DEVICE_NAME=Custom EVSE
DEVICE_NAME=ChargeFlow
#LEDs
LED_CHARGING=n
LED_CHARGING_GPIO=
LED_ERROR=n
LED_ERROR_GPIO=
LED_STOP=n
LED_STOP_GPIO=
led_blue=n
led_blue_GPIO=
led_red=n
led_red_GPIO=
led_green=n
led_green_GPIO=
#Button
BUTTON_WIFI_GPIO=32
@@ -35,32 +35,3 @@ SOCKET_LOCK=n
SOCKET_LOCK_A_GPIO=
SOCKET_LOCK_B_GPIO=
SOCKET_LOCK_DETECTION_GPIO=
#Energy meter (none | cur | cur_vlt)
ENERGY_METER=none
ENERGY_METER_THREE_PHASES=n
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
ENERGY_METER_L1_CUR_ADC_CHANNEL=
ENERGY_METER_L2_CUR_ADC_CHANNEL=
ENERGY_METER_L3_CUR_ADC_CHANNEL=
ENERGY_METER_CUR_SCALE=
#Energy meter internal (ENERGY_METER=cur_vlt)
ENERGY_METER_L1_VLT_ADC_CHANNEL=
ENERGY_METER_L2_VLT_ADC_CHANNEL=
ENERGY_METER_L3_VLT_ADC_CHANNEL=
ENERGY_METER_VLT_SCALE=
#Serial (SERIAL_X=none|uart|rs485)
SERIAL_1=none
SERIAL_1_NAME=UART 1
SERIAL_1_RXD_GPIO=
SERIAL_1_TXD_GPIO=
SERIAL_1_RTS_GPIO=
SERIAL_2=none
SERIAL_2_NAME=UART 2
SERIAL_2_RXD_GPIO=
SERIAL_2_TXD_GPIO=
SERIAL_2_RTS_GPIO=

View File

@@ -1,16 +1,16 @@
DEVICE_NAME=Plixin Evse
DEVICE_NAME=ChargeFlow
#LEDs
LED_CHARGING=y
LED_CHARGING_GPIO=14
LED_ERROR=y
LED_ERROR_GPIO=13
LED_STOP=y
LED_STOP_GPIO=12
led_blue=y
led_blue_GPIO=14
led_red=y
led_red_GPIO=26
led_green=y
led_green_GPIO=12
#BUZZER
BUZZER=y
BUZZER_GPIO=21
BUZZER_GPIO=27
#Button
BUTTON_WIFI_GPIO=32
@@ -42,84 +42,3 @@ SOCKET_LOCK=n
SOCKET_LOCK_A_GPIO=
SOCKET_LOCK_B_GPIO=
SOCKET_LOCK_DETECTION_GPIO=
#Energy meter (none | cur | cur_vlt)
ENERGY_METER=none
ENERGY_METER_THREE_PHASES=n
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
ENERGY_METER_L1_CUR_ADC_CHANNEL=7
ENERGY_METER_L2_CUR_ADC_CHANNEL=
ENERGY_METER_L3_CUR_ADC_CHANNEL=
ENERGY_METER_CUR_SCALE=0.090909091
#Energy meter internal (ENERGY_METER=cur_vlt)
ENERGY_METER_L1_VLT_ADC_CHANNEL=
ENERGY_METER_L2_VLT_ADC_CHANNEL=
ENERGY_METER_L3_VLT_ADC_CHANNEL=
ENERGY_METER_VLT_SCALE=0.47
#AUX
AUX_IN_1=n
AUX_IN_1_NAME=
AUX_IN_1_GPIO=
AUX_IN_2=n
AUX_IN_2_NAME=
AUX_IN_2_GPIO=
AUX_IN_3=n
AUX_IN_3_NAME=
AUX_IN_3_GPIO=
AUX_IN_4=n
AUX_IN_4_NAME=
AUX_IN_4_GPIO=
AUX_OUT_1=n
AUX_OUT_1_NAME=
AUX_OUT_1_GPIO=
AUX_OUT_2=n
AUX_OUT_2_NAME=
AUX_OUT_2_GPIO=
AUX_OUT_3=n
AUX_OUT_3_NAME=
AUX_OUT_3_GPIO=
AUX_OUT_4=n
AUX_OUT_4_NAME=
AUX_OUT_4_GPIO=
AUX_AIN_1=n
AUX_AIN_1_NAME=
AUX_AIN_1_ADC_CHANNEL=
AUX_AIN_2=n
AUX_AIN_2_NAME=
AUX_AIN_2_ADC_CHANNEL=
#Serial (SERIAL_X=none|uart|rs485)
SERIAL_1=none
SERIAL_1_NAME=UART via USB
SERIAL_1_RXD_GPIO=
SERIAL_1_TXD_GPIO=
SERIAL_1_RTS_GPIO=
SERIAL_2=none
SERIAL_2_NAME=RS485
SERIAL_2_RXD_GPIO=
SERIAL_2_TXD_GPIO=
SERIAL_2_RTS_GPIO=
SERIAL_3=none
SERIAL_3_NAME=UART
SERIAL_3_RXD_GPIO=
SERIAL_3_TXD_GPIO=
SERIAL_3_RTS_GPIO=
#Onewire devices
ONEWIRE=n
ONEWIRE_GPIO=
ONEWIRE_TEMP_SENSOR=n

View File

@@ -1,12 +1,12 @@
DEVICE_NAME=ESP32-S2-DA EVSE
DEVICE_NAME=ChargeFlow
#LEDs
LED_CHARGING=y
LED_CHARGING_GPIO=36
LED_ERROR=y
LED_ERROR_GPIO=37
LED_STOP=y
LED_STOP_GPIO=35
led_blue=y
led_blue_GPIO=36
led_red=y
led_red_GPIO=37
led_green=y
led_green_GPIO=35
#Button
BUTTON_WIFI_GPIO=32
@@ -42,78 +42,3 @@ SOCKET_LOCK_MIN_BREAK_TIME=1000
RCM=n
RCM_GPIO=41
RCM_TEST_GPIO=26
#Energy meter (none | cur | cur_vlt)
ENERGY_METER=cur_vlt
ENERGY_METER_THREE_PHASES=y
#Energy meter internal (ENERGY_METER=cur | cur_vlt)
ENERGY_METER_L1_CUR_ADC_CHANNEL=4
ENERGY_METER_L2_CUR_ADC_CHANNEL=5
ENERGY_METER_L3_CUR_ADC_CHANNEL=6
ENERGY_METER_CUR_SCALE=0.090909091
#Energy meter internal (ENERGY_METER=cur_vlt)
ENERGY_METER_L1_VLT_ADC_CHANNEL=7
ENERGY_METER_L2_VLT_ADC_CHANNEL=8
ENERGY_METER_L3_VLT_ADC_CHANNEL=9
ENERGY_METER_VLT_SCALE=0.47
#AUX
AUX_IN_1=n
AUX_IN_1_NAME=
AUX_IN_1_GPIO=
AUX_IN_2=n
AUX_IN_2_NAME=
AUX_IN_2_GPIO=
AUX_IN_3=n
AUX_IN_3_NAME=
AUX_IN_3_GPIO=
AUX_IN_4=n
AUX_IN_4_NAME=
AUX_IN_4_GPIO=
AUX_OUT_1=n
AUX_OUT_1_NAME=
AUX_OUT_1_GPIO=
AUX_OUT_2=n
AUX_OUT_2_NAME=
AUX_OUT_2_GPIO=
AUX_OUT_3=n
AUX_OUT_3_NAME=
AUX_OUT_3_GPIO=
AUX_OUT_4=n
AUX_OUT_4_NAME=
AUX_OUT_4_GPIO=
AUX_AIN_1=n
AUX_AIN_1_NAME=
AUX_AIN_1_ADC_CHANNEL=
AUX_AIN_2=n
AUX_AIN_2_NAME=
AUX_AIN_2_ADC_CHANNEL=
#Serial (SERIAL_X=none|uart|rs485)
SERIAL_1=none
SERIAL_1_NAME=UART
SERIAL_1_RXD_GPIO=
SERIAL_1_TXD_GPIO=
SERIAL_1_RTS_GPIO=
SERIAL_2=none
SERIAL_2_NAME=RS-485
SERIAL_2_RXD_GPIO=
SERIAL_2_TXD_GPIO=
SERIAL_2_RTS_GPIO=
#Onewire devices
ONEWIRE=n
ONEWIRE_GPIO=
ONEWIRE_TEMP_SENSOR=n

View File

@@ -1,14 +0,0 @@
set(srcs
"src/api.c"
"src/ota.c"
"src/json.c"
"src/timeout_utils.c"
"lib/cAT/src/cat.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" "lib/cAT/src"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash app_update json driver esp_http_client esp_netif esp_wifi esp_timer esp_hw_support
REQUIRES network config evse peripherals protocols meter_orno ocpp)
set_source_files_properties(lib/cAT/src/cat.c PROPERTIES COMPILE_FLAGS -Wno-maybe-uninitialized)

View File

@@ -1,6 +0,0 @@
#ifndef API_H_
#define API_H_
void api_init(void);
#endif /* API_H_ */

View File

@@ -1,43 +0,0 @@
#ifndef JSON_UTILS_H
#define JSON_UTILS_H
#include "esp_err.h"
#include "cJSON.h"
cJSON* json_get_evse_config(void);
esp_err_t json_set_evse_config(cJSON* root);
cJSON* json_get_wifi_config(void);
esp_err_t json_set_wifi_config(cJSON* root, bool timeout);
cJSON* json_get_wifi_scan(void);
cJSON* json_get_mqtt_config(void);
esp_err_t json_set_mqtt_config(cJSON* root);
//cJSON* json_get_serial_config(void);
//esp_err_t json_set_serial_config(cJSON* root);
cJSON* json_get_modbus_config(void);
esp_err_t json_set_modbus_config(cJSON* root);
//cJSON* json_get_script_config(void);
//esp_err_t json_set_script_config(cJSON* root);
cJSON* json_get_time_config(void);
esp_err_t json_set_time_config(cJSON* root);
cJSON* json_get_state(void);
cJSON* json_get_info(void);
cJSON* json_get_board_config(void);
#endif /* JSON_UTILS_H */

View File

@@ -1,15 +0,0 @@
#ifndef OTA_H_
#define OTA_H_
#include <stdbool.h>
#include "esp_err.h"
#define OTA_VERSION_URL "https://dzurikmiroslav.github.io/esp32-evse/firmware/version.txt"
#define OTA_FIRMWARE_URL "https://dzurikmiroslav.github.io/esp32-evse/firmware/"
esp_err_t ota_get_available_version(char* version);
bool ota_is_newer_version(const char* actual, const char* available);
#endif /* OTA_H_ */

View File

@@ -1,23 +0,0 @@
#ifndef TIMEOUT_UTILS_H
#define TIMEOUT_UTILS_H
#include <stdbool.h>
#include "esp_err.h"
/**
* @brief Restart in 5 seconds
*
*/
void timeout_restart(void);
/**
* @brief Set WiFi config in 1 second
*
* @param enabled
* @param ssid
* @param password
* @return esp_err_t
*/
esp_err_t timeout_wifi_set_config(bool enabled, const char* ssid, const char* password);
#endif /* TIMEOUT_UTILS_H */

View File

@@ -1,493 +0,0 @@
# SPDX-License-Identifier: GPL-2.0
#
# clang-format configuration file. Intended for clang-format >= 4.
#
# For more information, see:
#
# Documentation/process/clang-format.rst
# https://clang.llvm.org/docs/ClangFormat.html
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
#AfterExternBlock: false # Unknown to clang-format-5.0
BeforeCatch: false
BeforeElse: false
IndentBraces: false
#SplitEmptyFunction: true # Unknown to clang-format-4.0
#SplitEmptyRecord: true # Unknown to clang-format-4.0
#SplitEmptyNamespace: true # Unknown to clang-format-4.0
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 160
CommentPragmas: '^ IWYU pragma:'
#CompactNamespaces: false # Unknown to clang-format-4.0
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
#FixNamespaceComments: false # Unknown to clang-format-4.0
# Taken from:
# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \
# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
# | sort | uniq
ForEachMacros:
- 'apei_estatus_for_each_section'
- 'ata_for_each_dev'
- 'ata_for_each_link'
- '__ata_qc_for_each'
- 'ata_qc_for_each'
- 'ata_qc_for_each_raw'
- 'ata_qc_for_each_with_internal'
- 'ax25_for_each'
- 'ax25_uid_for_each'
- '__bio_for_each_bvec'
- 'bio_for_each_bvec'
- 'bio_for_each_integrity_vec'
- '__bio_for_each_segment'
- 'bio_for_each_segment'
- 'bio_for_each_segment_all'
- 'bio_list_for_each'
- 'bip_for_each_vec'
- 'blkg_for_each_descendant_post'
- 'blkg_for_each_descendant_pre'
- 'blk_queue_for_each_rl'
- 'bond_for_each_slave'
- 'bond_for_each_slave_rcu'
- 'bpf_for_each_spilled_reg'
- 'btree_for_each_safe128'
- 'btree_for_each_safe32'
- 'btree_for_each_safe64'
- 'btree_for_each_safel'
- 'card_for_each_dev'
- 'cgroup_taskset_for_each'
- 'cgroup_taskset_for_each_leader'
- 'cpufreq_for_each_entry'
- 'cpufreq_for_each_entry_idx'
- 'cpufreq_for_each_valid_entry'
- 'cpufreq_for_each_valid_entry_idx'
- 'css_for_each_child'
- 'css_for_each_descendant_post'
- 'css_for_each_descendant_pre'
- 'device_for_each_child_node'
- 'drm_atomic_crtc_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane_state'
- 'drm_atomic_for_each_plane_damage'
- 'drm_connector_for_each_possible_encoder'
- 'drm_for_each_connector_iter'
- 'drm_for_each_crtc'
- 'drm_for_each_encoder'
- 'drm_for_each_encoder_mask'
- 'drm_for_each_fb'
- 'drm_for_each_legacy_plane'
- 'drm_for_each_plane'
- 'drm_for_each_plane_mask'
- 'drm_for_each_privobj'
- 'drm_mm_for_each_hole'
- 'drm_mm_for_each_node'
- 'drm_mm_for_each_node_in_range'
- 'drm_mm_for_each_node_safe'
- 'flow_action_for_each'
- 'for_each_active_drhd_unit'
- 'for_each_active_iommu'
- 'for_each_available_child_of_node'
- 'for_each_bio'
- 'for_each_board_func_rsrc'
- 'for_each_bvec'
- 'for_each_card_components'
- 'for_each_card_links'
- 'for_each_card_links_safe'
- 'for_each_card_prelinks'
- 'for_each_card_rtds'
- 'for_each_card_rtds_safe'
- 'for_each_cgroup_storage_type'
- 'for_each_child_of_node'
- 'for_each_clear_bit'
- 'for_each_clear_bit_from'
- 'for_each_cmsghdr'
- 'for_each_compatible_node'
- 'for_each_component_dais'
- 'for_each_component_dais_safe'
- 'for_each_comp_order'
- 'for_each_console'
- 'for_each_cpu'
- 'for_each_cpu_and'
- 'for_each_cpu_not'
- 'for_each_cpu_wrap'
- 'for_each_dev_addr'
- 'for_each_dma_cap_mask'
- 'for_each_dpcm_be'
- 'for_each_dpcm_be_rollback'
- 'for_each_dpcm_be_safe'
- 'for_each_dpcm_fe'
- 'for_each_drhd_unit'
- 'for_each_dss_dev'
- 'for_each_efi_memory_desc'
- 'for_each_efi_memory_desc_in_map'
- 'for_each_element'
- 'for_each_element_extid'
- 'for_each_element_id'
- 'for_each_endpoint_of_node'
- 'for_each_evictable_lru'
- 'for_each_fib6_node_rt_rcu'
- 'for_each_fib6_walker_rt'
- 'for_each_free_mem_range'
- 'for_each_free_mem_range_reverse'
- 'for_each_func_rsrc'
- 'for_each_hstate'
- 'for_each_if'
- 'for_each_iommu'
- 'for_each_ip_tunnel_rcu'
- 'for_each_irq_nr'
- 'for_each_link_codecs'
- 'for_each_lru'
- 'for_each_matching_node'
- 'for_each_matching_node_and_match'
- 'for_each_memblock'
- 'for_each_memblock_type'
- 'for_each_memcg_cache_index'
- 'for_each_mem_pfn_range'
- 'for_each_mem_range'
- 'for_each_mem_range_rev'
- 'for_each_migratetype_order'
- 'for_each_msi_entry'
- 'for_each_msi_entry_safe'
- 'for_each_net'
- 'for_each_netdev'
- 'for_each_netdev_continue'
- 'for_each_netdev_continue_rcu'
- 'for_each_netdev_feature'
- 'for_each_netdev_in_bond_rcu'
- 'for_each_netdev_rcu'
- 'for_each_netdev_reverse'
- 'for_each_netdev_safe'
- 'for_each_net_rcu'
- 'for_each_new_connector_in_state'
- 'for_each_new_crtc_in_state'
- 'for_each_new_mst_mgr_in_state'
- 'for_each_new_plane_in_state'
- 'for_each_new_private_obj_in_state'
- 'for_each_node'
- 'for_each_node_by_name'
- 'for_each_node_by_type'
- 'for_each_node_mask'
- 'for_each_node_state'
- 'for_each_node_with_cpus'
- 'for_each_node_with_property'
- 'for_each_of_allnodes'
- 'for_each_of_allnodes_from'
- 'for_each_of_cpu_node'
- 'for_each_of_pci_range'
- 'for_each_old_connector_in_state'
- 'for_each_old_crtc_in_state'
- 'for_each_old_mst_mgr_in_state'
- 'for_each_oldnew_connector_in_state'
- 'for_each_oldnew_crtc_in_state'
- 'for_each_oldnew_mst_mgr_in_state'
- 'for_each_oldnew_plane_in_state'
- 'for_each_oldnew_plane_in_state_reverse'
- 'for_each_oldnew_private_obj_in_state'
- 'for_each_old_plane_in_state'
- 'for_each_old_private_obj_in_state'
- 'for_each_online_cpu'
- 'for_each_online_node'
- 'for_each_online_pgdat'
- 'for_each_pci_bridge'
- 'for_each_pci_dev'
- 'for_each_pci_msi_entry'
- 'for_each_populated_zone'
- 'for_each_possible_cpu'
- 'for_each_present_cpu'
- 'for_each_prime_number'
- 'for_each_prime_number_from'
- 'for_each_process'
- 'for_each_process_thread'
- 'for_each_property_of_node'
- 'for_each_registered_fb'
- 'for_each_reserved_mem_region'
- 'for_each_rtd_codec_dai'
- 'for_each_rtd_codec_dai_rollback'
- 'for_each_rtdcom'
- 'for_each_rtdcom_safe'
- 'for_each_set_bit'
- 'for_each_set_bit_from'
- 'for_each_sg'
- 'for_each_sg_dma_page'
- 'for_each_sg_page'
- 'for_each_sibling_event'
- 'for_each_subelement'
- 'for_each_subelement_extid'
- 'for_each_subelement_id'
- '__for_each_thread'
- 'for_each_thread'
- 'for_each_zone'
- 'for_each_zone_zonelist'
- 'for_each_zone_zonelist_nodemask'
- 'fwnode_for_each_available_child_node'
- 'fwnode_for_each_child_node'
- 'fwnode_graph_for_each_endpoint'
- 'gadget_for_each_ep'
- 'genradix_for_each'
- 'genradix_for_each_from'
- 'hash_for_each'
- 'hash_for_each_possible'
- 'hash_for_each_possible_rcu'
- 'hash_for_each_possible_rcu_notrace'
- 'hash_for_each_possible_safe'
- 'hash_for_each_rcu'
- 'hash_for_each_safe'
- 'hctx_for_each_ctx'
- 'hlist_bl_for_each_entry'
- 'hlist_bl_for_each_entry_rcu'
- 'hlist_bl_for_each_entry_safe'
- 'hlist_for_each'
- 'hlist_for_each_entry'
- 'hlist_for_each_entry_continue'
- 'hlist_for_each_entry_continue_rcu'
- 'hlist_for_each_entry_continue_rcu_bh'
- 'hlist_for_each_entry_from'
- 'hlist_for_each_entry_from_rcu'
- 'hlist_for_each_entry_rcu'
- 'hlist_for_each_entry_rcu_bh'
- 'hlist_for_each_entry_rcu_notrace'
- 'hlist_for_each_entry_safe'
- '__hlist_for_each_rcu'
- 'hlist_for_each_safe'
- 'hlist_nulls_for_each_entry'
- 'hlist_nulls_for_each_entry_from'
- 'hlist_nulls_for_each_entry_rcu'
- 'hlist_nulls_for_each_entry_safe'
- 'i3c_bus_for_each_i2cdev'
- 'i3c_bus_for_each_i3cdev'
- 'ide_host_for_each_port'
- 'ide_port_for_each_dev'
- 'ide_port_for_each_present_dev'
- 'idr_for_each_entry'
- 'idr_for_each_entry_continue'
- 'idr_for_each_entry_ul'
- 'inet_bind_bucket_for_each'
- 'inet_lhash2_for_each_icsk_rcu'
- 'key_for_each'
- 'key_for_each_safe'
- 'klp_for_each_func'
- 'klp_for_each_func_safe'
- 'klp_for_each_func_static'
- 'klp_for_each_object'
- 'klp_for_each_object_safe'
- 'klp_for_each_object_static'
- 'kvm_for_each_memslot'
- 'kvm_for_each_vcpu'
- 'list_for_each'
- 'list_for_each_codec'
- 'list_for_each_codec_safe'
- 'list_for_each_entry'
- 'list_for_each_entry_continue'
- 'list_for_each_entry_continue_rcu'
- 'list_for_each_entry_continue_reverse'
- 'list_for_each_entry_from'
- 'list_for_each_entry_from_rcu'
- 'list_for_each_entry_from_reverse'
- 'list_for_each_entry_lockless'
- 'list_for_each_entry_rcu'
- 'list_for_each_entry_reverse'
- 'list_for_each_entry_safe'
- 'list_for_each_entry_safe_continue'
- 'list_for_each_entry_safe_from'
- 'list_for_each_entry_safe_reverse'
- 'list_for_each_prev'
- 'list_for_each_prev_safe'
- 'list_for_each_safe'
- 'llist_for_each'
- 'llist_for_each_entry'
- 'llist_for_each_entry_safe'
- 'llist_for_each_safe'
- 'media_device_for_each_entity'
- 'media_device_for_each_intf'
- 'media_device_for_each_link'
- 'media_device_for_each_pad'
- 'mp_bvec_for_each_page'
- 'mp_bvec_for_each_segment'
- 'nanddev_io_for_each_page'
- 'netdev_for_each_lower_dev'
- 'netdev_for_each_lower_private'
- 'netdev_for_each_lower_private_rcu'
- 'netdev_for_each_mc_addr'
- 'netdev_for_each_uc_addr'
- 'netdev_for_each_upper_dev_rcu'
- 'netdev_hw_addr_list_for_each'
- 'nft_rule_for_each_expr'
- 'nla_for_each_attr'
- 'nla_for_each_nested'
- 'nlmsg_for_each_attr'
- 'nlmsg_for_each_msg'
- 'nr_neigh_for_each'
- 'nr_neigh_for_each_safe'
- 'nr_node_for_each'
- 'nr_node_for_each_safe'
- 'of_for_each_phandle'
- 'of_property_for_each_string'
- 'of_property_for_each_u32'
- 'pci_bus_for_each_resource'
- 'ping_portaddr_for_each_entry'
- 'plist_for_each'
- 'plist_for_each_continue'
- 'plist_for_each_entry'
- 'plist_for_each_entry_continue'
- 'plist_for_each_entry_safe'
- 'plist_for_each_safe'
- 'pnp_for_each_card'
- 'pnp_for_each_dev'
- 'protocol_for_each_card'
- 'protocol_for_each_dev'
- 'queue_for_each_hw_ctx'
- 'radix_tree_for_each_slot'
- 'radix_tree_for_each_tagged'
- 'rbtree_postorder_for_each_entry_safe'
- 'rdma_for_each_port'
- 'resource_list_for_each_entry'
- 'resource_list_for_each_entry_safe'
- 'rhl_for_each_entry_rcu'
- 'rhl_for_each_rcu'
- 'rht_for_each'
- 'rht_for_each_from'
- 'rht_for_each_entry'
- 'rht_for_each_entry_from'
- 'rht_for_each_entry_rcu'
- 'rht_for_each_entry_rcu_from'
- 'rht_for_each_entry_safe'
- 'rht_for_each_rcu'
- 'rht_for_each_rcu_from'
- '__rq_for_each_bio'
- 'rq_for_each_bvec'
- 'rq_for_each_segment'
- 'scsi_for_each_prot_sg'
- 'scsi_for_each_sg'
- 'sctp_for_each_hentry'
- 'sctp_skb_for_each'
- 'shdma_for_each_chan'
- '__shost_for_each_device'
- 'shost_for_each_device'
- 'sk_for_each'
- 'sk_for_each_bound'
- 'sk_for_each_entry_offset_rcu'
- 'sk_for_each_from'
- 'sk_for_each_rcu'
- 'sk_for_each_safe'
- 'sk_nulls_for_each'
- 'sk_nulls_for_each_from'
- 'sk_nulls_for_each_rcu'
- 'snd_array_for_each'
- 'snd_pcm_group_for_each_entry'
- 'snd_soc_dapm_widget_for_each_path'
- 'snd_soc_dapm_widget_for_each_path_safe'
- 'snd_soc_dapm_widget_for_each_sink_path'
- 'snd_soc_dapm_widget_for_each_source_path'
- 'tb_property_for_each'
- 'tcf_exts_for_each_action'
- 'udp_portaddr_for_each_entry'
- 'udp_portaddr_for_each_entry_rcu'
- 'usb_hub_for_each_child'
- 'v4l2_device_for_each_subdev'
- 'v4l2_m2m_for_each_dst_buf'
- 'v4l2_m2m_for_each_dst_buf_safe'
- 'v4l2_m2m_for_each_src_buf'
- 'v4l2_m2m_for_each_src_buf_safe'
- 'virtio_device_for_each_vq'
- 'xa_for_each'
- 'xa_for_each_marked'
- 'xa_for_each_start'
- 'xas_for_each'
- 'xas_for_each_conflict'
- 'xas_for_each_marked'
- 'zorro_for_each_dev'
#IncludeBlocks: Preserve # Unknown to clang-format-5.0
IncludeCategories:
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
#IndentPPDirectives: None # Unknown to clang-format-5.0
IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
# Taken from git's rules
#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
#SortUsingDeclarations: false # Unknown to clang-format-4.0
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
SpaceBeforeParens: ControlStatements
#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: Never
...

View File

@@ -1,17 +0,0 @@
.vscode
lib
bin
dist
build
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps

View File

@@ -1,11 +0,0 @@
language: c
compiler:
- gcc
before_script:
- mkdir build
- cd build
- cmake ..
script: make && make test

View File

@@ -1,148 +0,0 @@
cmake_minimum_required( VERSION 3.0 )
project( libcat LANGUAGES C )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
enable_testing( )
include_directories( ${PROJECT_SOURCE_DIR}/src )
file( GLOB SRC_FILES src/*.c )
add_library( cat SHARED ${SRC_FILES} )
set_target_properties( cat PROPERTIES VERSION 0.11.1 SOVERSION 1 )
target_compile_options( cat PRIVATE -Werror -Wall -Wextra -pedantic )
install( TARGETS cat DESTINATION lib )
install( FILES src/cat.h DESTINATION include/cat )
add_executable( demo example/demo.c )
target_link_libraries( demo cat )
add_executable( basic example/basic.c )
target_link_libraries( basic cat )
add_executable( unsolicited example/unsolicited.c )
target_link_libraries( unsolicited cat )
add_executable( test_parse tests/test_parse.c )
target_link_libraries( test_parse cat )
add_test( test_parse ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_parse )
add_executable( test_run tests/test_run.c )
target_link_libraries( test_run cat )
add_test( test_run ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_run )
add_executable( test_read tests/test_read.c )
target_link_libraries( test_read cat )
add_test( test_read ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_read )
add_executable( test_write tests/test_write.c )
target_link_libraries( test_write cat )
add_test( test_write ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write )
add_executable( test_write_parse tests/test_write_parse.c )
target_link_libraries( test_write_parse cat )
add_test( test_write_parse ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write_parse )
add_executable( test_write_int_range tests/test_write_int_range.c )
target_link_libraries( test_write_int_range cat )
add_test( test_write_int_range ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write_int_range )
add_executable( test_write_uint_range tests/test_write_uint_range.c )
target_link_libraries( test_write_uint_range cat )
add_test( test_write_uint_range ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write_uint_range )
add_executable( test_write_hex_range tests/test_write_hex_range.c )
target_link_libraries( test_write_hex_range cat )
add_test( test_write_hex_range ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write_hex_range )
add_executable( test_write_hex_buffer tests/test_write_hex_buffer.c )
target_link_libraries( test_write_hex_buffer cat )
add_test( test_write_hex_buffer ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write_hex_buffer )
add_executable( test_write_string_buffer tests/test_write_string_buffer.c )
target_link_libraries( test_write_string_buffer cat )
add_test( test_write_string_buffer ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_write_string_buffer )
add_executable( test_read_args tests/test_read_args.c )
target_link_libraries( test_read_args cat )
add_test( test_read_args ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_read_args )
add_executable( test_test tests/test_test.c )
target_link_libraries( test_test cat )
add_test( test_test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_test )
add_executable( test_test_args tests/test_test_args.c )
target_link_libraries( test_test_args cat )
add_test( test_test_args ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_test_args )
add_executable( test_mutex tests/test_mutex.c )
target_link_libraries( test_mutex cat )
add_test( test_mutex ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_mutex )
add_executable( test_unsolicited_read tests/test_unsolicited_read.c )
target_link_libraries( test_unsolicited_read cat )
add_test( test_unsolicited_read ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_unsolicited_read )
add_executable( test_unsolicited_read_stress tests/test_unsolicited_read_stress.c )
target_link_libraries( test_unsolicited_read_stress cat )
add_test( test_unsolicited_read_stress ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_unsolicited_read_stress )
add_executable( test_unsolicited_read_buffer tests/test_unsolicited_read_buffer.c ${SRC_FILES})
set_target_properties( test_unsolicited_read_buffer PROPERTIES COMPILE_DEFINITIONS "CAT_UNSOLICITED_CMD_BUFFER_SIZE=2" )
add_test( test_unsolicited_read_buffer ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_unsolicited_read_buffer )
add_executable( test_hold_state tests/test_hold_state.c )
target_link_libraries( test_hold_state cat )
add_test( test_hold_state ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_hold_state )
add_executable( test_return_read tests/test_return_read.c )
target_link_libraries( test_return_read cat )
add_test( test_return_read ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_return_read )
add_executable( test_return_write tests/test_return_write.c )
target_link_libraries( test_return_write cat )
add_test( test_return_write ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_return_write )
add_executable( test_unsolicited_test tests/test_unsolicited_test.c )
target_link_libraries( test_unsolicited_test cat )
add_test( test_unsolicited_test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_unsolicited_test )
add_executable( test_return_test tests/test_return_test.c )
target_link_libraries( test_return_test cat )
add_test( test_return_test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_return_test )
add_executable( test_return_run tests/test_return_run.c )
target_link_libraries( test_return_run cat )
add_test( test_return_run ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_return_run )
add_executable( test_test_only tests/test_test_only.c )
target_link_libraries( test_test_only cat )
add_test( test_test_only ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_test_only )
add_executable( test_search_cmd tests/test_search_cmd.c )
target_link_libraries( test_search_cmd cat )
add_test( test_search_cmd ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_search_cmd )
add_executable( test_order tests/test_order.c )
target_link_libraries( test_order cat )
add_test( test_order ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_order )
add_executable( test_cmd_list tests/test_cmd_list.c )
target_link_libraries( test_cmd_list cat )
add_test( test_cmd_list ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_cmd_list )
add_executable( test_var_access tests/test_var_access.c )
target_link_libraries( test_var_access cat )
add_test( test_var_access ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_var_access )
add_executable( test_shortcuts tests/test_shortcuts.c )
target_link_libraries( test_shortcuts cat )
add_test( test_shortcuts ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_shortcuts )
add_custom_target( check COMMAND ${CMAKE_CTEST_COMMAND} --verbose )
add_custom_target( cleanall COMMAND rm -rf Makefile CMakeCache.txt CMakeFiles/ bin/ lib/ cmake_install.cmake CTestTestfile.cmake Testing/ )
add_custom_target( uninstall COMMAND xargs rm < install_manifest.txt )

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,211 +0,0 @@
[![Build Status](https://travis-ci.org/marcinbor85/cat.svg?branch=master)](https://travis-ci.org/marcinbor85/cat)
# libcat (cAT)
Plain C library for parsing AT commands for use in host devices.
## Features
* blazing fast, non-blocking, robust implementation
* 100% static implementation (without any dynamic memory allocation)
* very small footprint (both RAM and ROM)
* support for READ, WRITE, TEST and RUN type commands
* commands shortcuts (auto select best command candidate)
* single request - multiple responses
* unsolicited read/test command support
* hold state for delayed responses for time-consuming tasks
* high-level memory variables mapping arguments parsing
* variables accessors (read and write, read only, write only)
* automatic arguments types validating
* automatic format test responses for commands with variables
* CRLF and LF compatible
* case-insensitive
* dedicated for embedded systems
* object-oriented architecture
* separated interface for low-level layer
* fully asynchronous input/output operations
* multiplatform and portable
* asynchronous api with event callbacks
* print registered commands list feature
* only two source files
* wide unit tests
## Build
Build and install:
```sh
cmake .
make
make test
sudo make install
```
## Example basic demo posibilities
```console
AT+PRINT=? # TEST command
+PRINT=<X:UINT8[RW]>,<Y:UINT8[RW]>,<MESSAGE:STRING[RW]> # Automatic response
Printing something special at (X,Y). # Automatic response
OK # Automatic acknowledge
AT+PRINT? # READ command
+PRINT=0,0,"" # Automatic response
OK # Automatic acknowledge
AT+PRINT=xyz,-2 # WRITE command
ERROR # Automatic acknowledge
AT+PRINT=1,2,"test" # WRITE command
OK # Automatic acknowledge
AT+PRINT # RUN command
some printing at (1,2) with text "test" # Manual response
OK # Automatic acknowledge
```
## Example unsolicited demo posibilities
```console
AT+START=? # TEST command
+START=<MODE:UINT32[WO]> # Automatic response
Start scanning after write (0 - wifi, 1 - bluetooth). # Automatic response
OK # Automatic acknowledge
AT+START=0 # WRITE command
+SCAN=-10,"wifi1" # Unsolicited read response
+SCAN=-50,"wifi2" # Unsolicited read response
+SCAN=-20,"wifi3" # Unsolicited read response
OK # Unsolicited acknowledge
AT+START=1 # WRITE command
+SCAN=-20,"bluetooth1" # Unsolicited read response
OK # Unsolicited acknowledge
AT+SCAN=? # TEST command
+SCAN=<RSSI:INT32[RO]>,<SSID:STRING[RO]> # Automatic response
Scan result record. # Automatic response
OK # Automatic acknowledge
```
## Usage
Define High-Level variables:
```c
static uint8_t x;
static uint8_t y;
static char msg[32];
static struct cat_variable go_vars[] = {
{
.type = CAT_VAR_UINT_DEC, /* unsigned int variable */
.data = &x,
.data_size = sizeof(x),
.write = x_write,
.name = "X",
.access = CAT_VAR_ACCESS_READ_WRITE,
},
{
.type = CAT_VAR_UINT_DEC, /* unsigned int variable */
.data = &y,
.data_size = sizeof(y),
.write = y_write,
.access = CAT_VAR_ACCESS_READ_WRITE,
},
{
.type = CAT_VAR_BUF_STRING, /* string variable */
.data = msg,
.data_size = sizeof(msg),
.write = msg_write,
.access = CAT_VAR_ACCESS_READ_WRITE,
}
};
```
Define AT commands descriptor:
```c
static struct cat_command cmds[] = {
{
.name = "TEST",
.read = test_read, /* read handler for ATTEST? command */
.write = test_write, /* write handler for ATTEST={val} command */
.run = test_run /* run handler for ATTEST command */
},
{
.name = "+NUM",
.write = num_write, /* write handler for AT+NUM={val} command */
.read = num_read /* read handler for AT+NUM? command */
},
{
.name = "+GO",
.write = go_write, /* write handler for AT+GO={x},{y},{msg} command */
.var = go_vars, /* attach variables to command */
.var_num = sizeof(go_vars) / sizeof(go_vars[0]),
.need_all_vars = true
},
{
.name = "RESTART",
.run = restart_run /* run handler for ATRESTART command */
}
};
```
Define AT command parser descriptor:
```c
static char working_buf[128]; /* working buffer, must be declared manually */
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = working_buf,
.buf_size = sizeof(working_buf),
};
```
Define IO low-level layer interface:
```c
static int write_char(char ch)
{
putc(ch, stdout);
return 1;
}
static int read_char(char *ch)
{
*ch = getch();
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
```
Initialize AT command parser and run:
```c
struct cat_object at; /* at command parser object */
cat_init(&at, &desc, &iface, NULL); /* initialize at command parser object */
while (1) {
cat_service(&at) /* periodically call at command parser service */
... /* other stuff, running in main loop */
}
```

View File

@@ -1,70 +0,0 @@
TODO:
- differentation of variables for read and write commands
- help command (printing all commands posibilities with descriptions)
- documentation updated (buffer sized, return enum types, write variable nums, buf size hints)
- helper setters and getters for variables
0.11.0
* optional context in commands and data_getter in variables
* optional extended read and write variable handler with parent command
0.10.1
* single working buffer insteads of two separated for atcmd and unsolicited events
0.10.0
* separate atcmd and unsolicited event state machines
0.9.0
* variables accessors
0.8.1
* fix commands shourtcuts
0.8.0
* print commands list feature
0.7.1
* more flexible command group initialization (pointers)
0.7.0
* function for checking unsolicited buffer
* some refactoring regarding unsolicited buffer
* helper function cat_search_variable_by_name added
* commands group with searching feature added
* disable feature for commands and groups added
0.6.0
* async non blocking io->write
* async unsolicited read/test event (injected at idle and hold state)
* hold commands parse processing feature (for delayed unsolicited read responses)
* behavior differentation by callbacks return values
* only-test flag in commands descriptor (for fast disable read/write/run)
* helper function for searching command by name
* multiple responses triggered by single request pattern support
* generic mutex descriptor interface
* return value states enum types
0.5.3
* function for checking internal busy state added
0.5.2
* small fix in auto description response
0.5.1
* description field in command added (used in auto help message)
0.5.0
* test command parser with auto help messages
* output string end-line encoding same as input string
* very basic demo example application added
0.4.0
* high level parsing for command arguments
* int, uint, hex, hexbuf, string argument types
* validating arguments values range
0.3.0
* independent buffers (parsing buffer and state buffer)
0.2.0
* passing command struct instead of command name in command handlers

View File

@@ -1,161 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
/* variables assigned to print command */
static uint8_t x;
static uint8_t y;
static char message[16];
/* helper variable used to exit demo code */
static bool quit_flag;
/* run command handler with application dependent login print code */
static int print_run(const struct cat_command *cmd)
{
printf("some printing at (%d,%d) with text \"%s\"\n", x, y, message);
return 0;
}
/* run command handler attached to HELP command for printing commands list */
static int print_cmd_list(const struct cat_command *cmd)
{
return CAT_RETURN_STATE_PRINT_CMD_LIST_OK;
}
/* run command handler with custom exit mechanism */
static int quit_run(const struct cat_command *cmd)
{
quit_flag = true;
return 0;
}
/* declaring print variables array */
static struct cat_variable print_vars[] = {
{
.type = CAT_VAR_UINT_DEC,
.data = &x,
.data_size = sizeof(x),
.name = "X",
.access = CAT_VAR_ACCESS_READ_WRITE,
},
{
.type = CAT_VAR_UINT_DEC,
.data = &y,
.data_size = sizeof(y),
.name = "Y",
.access = CAT_VAR_ACCESS_READ_WRITE,
},
{
.type = CAT_VAR_BUF_STRING,
.data = message,
.data_size = sizeof(message),
.name = "MESSAGE",
.access = CAT_VAR_ACCESS_READ_WRITE,
}
};
/* declaring commands array */
static struct cat_command cmds[] = {
{
.name = "+PRINT",
.description = "Printing something special at (X,Y).",
.run = print_run,
.var = print_vars,
.var_num = sizeof(print_vars) / sizeof(print_vars[0]),
.need_all_vars = true
},
{
.name = "#HELP",
.run = print_cmd_list,
},
{
.name = "#QUIT",
.run = quit_run
},
};
/* working buffer */
static char buf[128];
/* declaring parser descriptor */
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
/* custom target dependent input output handlers */
static int write_char(char ch)
{
putc(ch, stdout);
return 1;
}
static int read_char(char *ch)
{
*ch = getc(stdin);
return 1;
}
/* declaring input output interface descriptor for parser */
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
int main(int argc, char **argv)
{
struct cat_object at;
/* initializing */
cat_init(&at, &desc, &iface, NULL);
/* main loop with exit code conditions */
while ((cat_service(&at) != 0) && (quit_flag == 0)) {};
/* goodbye message */
printf("Bye!\n");
return 0;
}

View File

@@ -1,260 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static int32_t speed;
static uint16_t adr;
static uint8_t x;
static uint8_t y;
static uint8_t bytes_buf[4];
static char msg[16];
static bool quit_flag;
static int x_write(const struct cat_variable *var, size_t write_size)
{
printf("x variable updated internally to: %u\n", x);
return 0;
}
static int y_write(const struct cat_variable *var, size_t write_size)
{
printf("y variable updated internally to: %u\n", y);
return 0;
}
static int msg_write(const struct cat_variable *var, size_t write_size)
{
printf("msg variable updated %lu bytes internally to: <%s>\n", write_size, msg);
return 0;
}
static int speed_write(const struct cat_variable *var, size_t write_size)
{
printf("speed variable updated internally to: %d\n", speed);
return 0;
}
static int adr_write(const struct cat_variable *var, size_t write_size)
{
printf("adr variable updated internally to: 0x%04X\n", adr);
return 0;
}
static int bytesbuf_write(const struct cat_variable *var, size_t write_size)
{
int i = 0;
printf("bytes_buf variable updated %lu bytes internally to: ", write_size);
for (i = 0; i < sizeof(bytes_buf); i++)
printf("%02X", bytes_buf[i]);
printf("\n");
return 0;
}
static int go_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
int i = 0;
printf("<%s>: x=%d y=%d msg=%s @ speed=%d\n",
cmd->name,
*(uint8_t*)(cmd->var[0].data),
*(uint8_t*)(cmd->var[1].data),
msg,
speed
);
printf("<bytes>: ");
for (i = 0; i < sizeof(bytes_buf); i++)
printf("%02X", bytes_buf[i]);
printf("\n");
return 0;
}
static int set_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
printf("<%s>: SET SPEED TO = %d\n",
cmd->name,
speed
);
return 0;
}
static int set_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int test_run(const struct cat_command *cmd)
{
printf("TEST: <%s>", cmd->name);
return 0;
}
static int quit_run(const struct cat_command *cmd)
{
printf("QUIT: <%s>", cmd->name);
quit_flag = true;
return 0;
}
static int print_cmd_list(const struct cat_command *cmd)
{
return CAT_RETURN_STATE_PRINT_CMD_LIST_OK;
}
static struct cat_variable go_vars[] = {
{
.type = CAT_VAR_UINT_DEC,
.data = &x,
.data_size = sizeof(x),
.write = x_write,
.name = "x"
},
{
.type = CAT_VAR_UINT_DEC,
.data = &y,
.data_size = sizeof(y),
.write = y_write,
.name = "y"
},
{
.type = CAT_VAR_BUF_STRING,
.data = msg,
.data_size = sizeof(msg),
.write = msg_write,
.name = "msg"
}
};
static struct cat_variable set_vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &speed,
.data_size = sizeof(speed),
.write = speed_write,
.name = "speed"
},
{
.type = CAT_VAR_NUM_HEX,
.data = &adr,
.data_size = sizeof(adr),
.write = adr_write,
.name = "address"
},
{
.type = CAT_VAR_BUF_HEX,
.data = &bytes_buf,
.data_size = sizeof(bytes_buf),
.write = bytesbuf_write,
.name = "buffer"
}
};
static struct cat_command cmds[] = {
{
.name = "+GO",
.write = go_write,
.var = go_vars,
.var_num = sizeof(go_vars) / sizeof(go_vars[0]),
.need_all_vars = true
},
{
.name = "+SET",
.write = set_write,
.read = set_read,
.var = set_vars,
.var_num = sizeof(set_vars) / sizeof(set_vars[0]),
},
{
.name = "#TEST",
.run = test_run
},
{
.name = "#HELP",
.run = print_cmd_list,
},
{
.name = "#QUIT",
.run = quit_run
},
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
putc(ch, stdout);
return 1;
}
static int read_char(char *ch)
{
*ch = getc(stdin);
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
while ((cat_service(&at) != 0) && (quit_flag == 0)) {};
printf("Bye!\n");
return 0;
}

View File

@@ -1,251 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
/* variables assigned to scan command */
static int rssi;
static char ssid[16];
/* helper variable used to exit demo code */
static bool quit_flag;
/* variables assigned to start command */
static int mode;
/* main at command parser object */
struct cat_object at;
struct scan_results {
int rssi;
char ssid[16];
};
/* static const scan results */
static const struct scan_results results[2][3] = {
{
{
.rssi = -10,
.ssid = "wifi1",
},
{
.rssi = -50,
.ssid = "wifi2",
},
{
.rssi = -20,
.ssid = "wifi3",
}
},
{
{
.rssi = -20,
.ssid = "bluetooth1",
}
}
};
/* helper variable */
static int scan_index;
/* run command handler with custom exit mechanism */
static int quit_run(const struct cat_command *cmd)
{
quit_flag = true;
return 0;
}
/* helper function */
static void load_scan_results(int index)
{
rssi = results[mode][index].rssi;
strcpy(ssid, results[mode][index].ssid);
}
/* declaring scan variables array */
static struct cat_variable scan_vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &rssi,
.data_size = sizeof(rssi),
.name = "RSSI"
},
{
.type = CAT_VAR_BUF_STRING,
.data = ssid,
.data_size = sizeof(ssid),
.name = "SSID"
}
};
/* forward declaration */
static cat_return_state scan_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size);
/* unsolicited command */
static struct cat_command scan_cmd = {
.name = "+SCAN",
.read = scan_read,
.var = scan_vars,
.var_num = sizeof(scan_vars) / sizeof(scan_vars[0])
};
/* unsolicited read callback handler */
static cat_return_state scan_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
int max = (mode == 0) ? 3 : 1;
scan_index++;
if (scan_index > max)
return CAT_RETURN_STATE_HOLD_EXIT_OK;
load_scan_results(scan_index);
cat_trigger_unsolicited_read(&at, &scan_cmd);
return CAT_RETURN_STATE_DATA_NEXT;
}
/* mode variable validator */
static int mode_write(const struct cat_variable *var, const size_t write_size)
{
if (*(int*)var->data >= 2)
return -1;
return 0;
}
/* start write callback handler */
static cat_return_state start_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
scan_index = 0;
load_scan_results(scan_index);
cat_trigger_unsolicited_read(&at, &scan_cmd);
return CAT_RETURN_STATE_HOLD;
}
/* run command handler attached to HELP command for printing commands list */
static int print_cmd_list(const struct cat_command *cmd)
{
return CAT_RETURN_STATE_PRINT_CMD_LIST_OK;
}
/* declaring start command variables array */
static struct cat_variable start_vars[] = {
{
.type = CAT_VAR_UINT_DEC,
.data = &mode,
.data_size = sizeof(mode),
.name = "MODE",
.write = mode_write,
.access = CAT_VAR_ACCESS_WRITE_ONLY,
}
};
/* declaring commands array */
static struct cat_command cmds[] = {
{
.name = "+START",
.description = "Start scanning after write (0 - wifi, 1 - bluetooth).",
.write = start_write,
.var = start_vars,
.var_num = sizeof(start_vars) / sizeof(start_vars[0]),
.need_all_vars = true
},
{
.name = "+SCAN",
.description = "Scan result record.",
.only_test = true,
.var = scan_vars,
.var_num = sizeof(scan_vars) / sizeof(scan_vars[0])
},
{
.name = "#HELP",
.run = print_cmd_list,
},
{
.name = "#QUIT",
.run = quit_run
},
};
/* working buffer */
static char buf[128];
/* declaring parser descriptor */
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
/* custom target dependent input output handlers */
static int write_char(char ch)
{
putc(ch, stdout);
return 1;
}
static int read_char(char *ch)
{
*ch = getc(stdin);
return 1;
}
/* declaring input output interface descriptor for parser */
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
int main(int argc, char **argv)
{
/* initializing */
cat_init(&at, &desc, &iface, NULL);
/* main loop with exit code conditions */
while ((cat_service(&at) != 0) && (quit_flag == 0)) {};
/* goodbye message */
printf("Bye!\n");
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,557 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef CAT_H
#define CAT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
/* only forward declarations (looks for definition below) */
struct cat_command;
struct cat_variable;
#ifndef CAT_UNSOLICITED_CMD_BUFFER_SIZE
/* unsolicited command buffer default size (can by override externally during compilation) */
#define CAT_UNSOLICITED_CMD_BUFFER_SIZE ((size_t)(1))
#endif
/* enum type with variable type definitions */
typedef enum {
CAT_VAR_INT_DEC = 0, /* decimal encoded signed integer variable */
CAT_VAR_UINT_DEC, /* decimal encoded unsigned integer variable */
CAT_VAR_NUM_HEX, /* hexadecimal encoded unsigned integer variable */
CAT_VAR_BUF_HEX, /* asciihex encoded bytes array */
CAT_VAR_BUF_STRING /* string variable */
} cat_var_type;
/* enum type wirh variable accessors definitions */
typedef enum {
CAT_VAR_ACCESS_READ_WRITE = 0, /* there will be possible to read and write variable */
CAT_VAR_ACCESS_READ_ONLY, /* there will be possible to read only variable */
CAT_VAR_ACCESS_WRITE_ONLY, /* there will be possible to write only variable */
} cat_var_access;
/* enum type with function status */
typedef enum {
CAT_STATUS_ERROR_BUFFER_EMPTY = -7,
CAT_STATUS_ERROR_NOT_HOLD = -6,
CAT_STATUS_ERROR_BUFFER_FULL = -5,
CAT_STATUS_ERROR_UNKNOWN_STATE = -4,
CAT_STATUS_ERROR_MUTEX_LOCK = -3,
CAT_STATUS_ERROR_MUTEX_UNLOCK = -2,
CAT_STATUS_ERROR = -1,
CAT_STATUS_OK = 0,
CAT_STATUS_BUSY = 1,
CAT_STATUS_HOLD = 2
} cat_status;
/**
* Write variable function handler
*
* This callback function is called after variable update immediatly.
* User application can validate writed value and check for amount of data size was written.
* This handler is optional, so when is not defined, operations will be fully automatically.
*
* @param var - pointer to struct descriptor of parsed variable
* @param write_size - size of data was written (useful especially with hexbuf var type)
* @return 0 - ok, else error and stop parsing
* */
typedef int (*cat_var_write_handler)(const struct cat_variable *var, const size_t write_size);
/**
* Read variable function handler
*
* This callback function is called just before variable value read.
* User application can copy data from private fields to variable connected with parsed command.
* This handler is optional, so when is not defined, operations will be fully automatically.
*
* @param var - pointer to struct descriptor of parsed variable
* @return 0 - ok, else error and stop parsing
* */
typedef int (*cat_var_read_handler)(const struct cat_variable *var);
/**
* Write variable extended function handler
*
* This callback function is called after variable update immediatly.
* User application can validate writed value and check for amount of data size was written.
* This handler is optional, so when is not defined, operations will be fully automatically.
*
* @param var - pointer to struct descriptor of parsed variable
* @param cmd - pointer to variable parent command structure
* @param write_size - size of data was written (useful especially with hexbuf var type)
* @return 0 - ok, else error and stop parsing
* */
typedef int (*cat_var_write_ex_handler)(const struct cat_variable *var, const struct cat_command *cmd, const size_t write_size);
/**
* Read variable function handler
*
* This callback function is called just before variable value read.
* User application can copy data from private fields to variable connected with parsed command.
* This handler is optional, so when is not defined, operations will be fully automatically.
*
* @param var - pointer to struct descriptor of parsed variable
* @param cmd - pointer to variable parent command structure
* @return 0 - ok, else error and stop parsing
* */
typedef int (*cat_var_read_ex_handler)(const struct cat_variable *var, const struct cat_command *cmd);
/**
* Data getter for variable.
* The purpose of this function is to replace statically allocated data and data_size fields
* with function pointer with context. Then the data and data_size from this getter would be
* changed and returned runtime.
*
* @param var - pointer to struct descriptor of parsed variable
* @param context - command context
* @param size - pointer to variable where should be placed data size
* @return pointer to data array from context
*/
typedef void* (*cat_var_data_getter)(const struct cat_variable *var, void *context, size_t *data_size);
struct cat_variable {
const char *name; /* variable name (optional - using only for auto format test command response) */
cat_var_type type; /* variable type (needed for parsing and validating) */
void *data; /* generic pointer to statically allocated memory for variable data read/write/validate operations */
size_t data_size; /* variable data size, pointed by data pointer */
cat_var_access access; /* variable accessor */
cat_var_write_handler write; /* write variable handler */
cat_var_read_handler read; /* read variable handler */
cat_var_write_ex_handler write_ex; /* optional write variable extended handler */
cat_var_read_ex_handler read_ex; /* optional read variable extended handler */
cat_var_data_getter data_getter; /* optional data getter for dynamic linking data and data_size */
};
/* enum type with command callbacks return values meaning */
typedef enum {
CAT_RETURN_STATE_ERROR = -1, /* immediatly error acknowledge */
CAT_RETURN_STATE_DATA_OK, /* send current data buffer followed by ok acknowledge */
CAT_RETURN_STATE_DATA_NEXT, /* send current data buffer and go to next callback iteration */
CAT_RETURN_STATE_NEXT, /* go to next callback iteration without sending anything */
CAT_RETURN_STATE_OK, /* immediatly ok acknowledge */
CAT_RETURN_STATE_HOLD, /* enable hold parser state */
CAT_RETURN_STATE_HOLD_EXIT_OK, /* exit from hold state with OK response */
CAT_RETURN_STATE_HOLD_EXIT_ERROR, /* exit from hold state with ERROR response */
CAT_RETURN_STATE_PRINT_CMD_LIST_OK, /* print commands list followed by ok acknowledge (only in TEST and RUN) */
} cat_return_state;
/**
* Write command function handler (AT+CMD=)
*
* This callback function is called after parsing all connected variables.
* User application can validate all variales at once at this moment, or copy them to the other application layer buffer.
* User can check number of variables or make custom process of parsing non standard arguments format.
* This handler is optional, so when is not defined, operations on variables will be fully automatically.
* If neither write handler nor variables not defined, then write command type is not available.
*
* @param cmd - pointer to struct descriptor of processed command
* @param data - pointer to arguments buffer for custom parsing
* @param data_size - length of arguments buffer
* @param args_num - number of passed arguments connected to variables
* @return according to cat_return_state enum definitions
* */
typedef cat_return_state (*cat_cmd_write_handler)(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num);
/**
* Read command function handler (AT+CMD?)
*
* This callback function is called after format all connected variables.
* User application can change automatic response, or add some custom data to response.
* This handler is optional, so when is not defined, operations on variables will be fully automatically.
* If neither read handler nor variables not defined, then read command type is not available.
*
* @param cmd - pointer to struct descriptor of processed command
* @param data - pointer to arguments buffer for custom parsing
* @param data_size - pointer to length of arguments buffer (can be modifed if needed)
* @param max_data_size - maximum length of buffer pointed by data pointer
* @return according to cat_return_state enum definitions
* */
typedef cat_return_state (*cat_cmd_read_handler)(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size);
/**
* Run command function handler (AT+CMD)
*
* No operations on variables are done.
* This handler is optional.
* If run handler not defined, then run command type is not available.
*
* @param cmd - pointer to struct descriptor of processed command
* @return according to cat_return_state enum definitions
* */
typedef cat_return_state (*cat_cmd_run_handler)(const struct cat_command *cmd);
/**
* Test command function handler (AT+CMD=?)
*
* This callback function is called after format all connected variables.
* User application can change automatic response, or add some custom data to response.
* This handler is optional, so when is not defined, operations on variables will be fully automatically.
* If neither test handler nor variables not defined, then test command type is not available.
* Exception of this rule is write command without variables.
* In this case, the "question mark" will be passed as a custom argument to the write handler.
*
* @param cmd - pointer to struct descriptor of processed command
* @param data - pointer to arguments buffer for custom parsing
* @param data_size - pointer to length of arguments buffer (can be modifed if needed)
* @param max_data_size - maximum length of buffer pointed by data pointer
* @return according to cat_return_state enum definitions
* */
typedef cat_return_state (*cat_cmd_test_handler)(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size);
/* enum type with main at parser fsm state */
typedef enum {
CAT_STATE_ERROR = -1,
CAT_STATE_IDLE,
CAT_STATE_PARSE_PREFIX,
CAT_STATE_PARSE_COMMAND_CHAR,
CAT_STATE_UPDATE_COMMAND_STATE,
CAT_STATE_WAIT_READ_ACKNOWLEDGE,
CAT_STATE_SEARCH_COMMAND,
CAT_STATE_COMMAND_FOUND,
CAT_STATE_COMMAND_NOT_FOUND,
CAT_STATE_PARSE_COMMAND_ARGS,
CAT_STATE_PARSE_WRITE_ARGS,
CAT_STATE_FORMAT_READ_ARGS,
CAT_STATE_WAIT_TEST_ACKNOWLEDGE,
CAT_STATE_FORMAT_TEST_ARGS,
CAT_STATE_WRITE_LOOP,
CAT_STATE_READ_LOOP,
CAT_STATE_TEST_LOOP,
CAT_STATE_RUN_LOOP,
CAT_STATE_HOLD,
CAT_STATE_FLUSH_IO_WRITE_WAIT,
CAT_STATE_FLUSH_IO_WRITE,
CAT_STATE_AFTER_FLUSH_RESET,
CAT_STATE_AFTER_FLUSH_OK,
CAT_STATE_AFTER_FLUSH_FORMAT_READ_ARGS,
CAT_STATE_AFTER_FLUSH_FORMAT_TEST_ARGS,
CAT_STATE_PRINT_CMD,
} cat_state;
/* enum type with type of command request */
typedef enum {
CAT_CMD_TYPE_NONE = -1,
CAT_CMD_TYPE_RUN,
CAT_CMD_TYPE_READ,
CAT_CMD_TYPE_WRITE,
CAT_CMD_TYPE_TEST,
CAT_CMD_TYPE__TOTAL_NUM
} cat_cmd_type;
/* structure with io interface functions */
struct cat_io_interface {
int (*write)(char ch); /* write char to output stream. return 1 if byte wrote successfully. */
int (*read)(char *ch); /* read char from input stream. return 1 if byte read successfully. */
};
/* structure with mutex interface functions */
struct cat_mutex_interface {
int (*lock)(void); /* lock mutex handler. return 0 if successfully locked, otherwise - cannot lock */
int (*unlock)(void); /* unlock mutex handler. return 0 if successfully unlocked, otherwise - cannot unlock */
};
/* structure with at command descriptor */
struct cat_command {
const char *name; /* at command name (case-insensitivity) */
const char *description; /* at command description (optionally - can be null) */
cat_cmd_write_handler write; /* write command handler */
cat_cmd_read_handler read; /* read command handler */
cat_cmd_run_handler run; /* run command handler */
cat_cmd_test_handler test; /* test command handler */
struct cat_variable const *var; /* pointer to array of variables assiocated with this command */
size_t var_num; /* number of variables in array */
bool need_all_vars; /* flag to need all vars parsing */
bool only_test; /* flag to disable read/write/run commands (only test auto description) */
bool disable; /* flag to completely disable command */
void* context; /* pointer to optional context structure */
};
struct cat_command_group {
const char *name; /* command group name (optional, for identification purpose) */
struct cat_command const *cmd; /* pointer to array of commands descriptor */
size_t cmd_num; /* number of commands in array */
bool disable; /* flag to completely disable all commands in group */
};
/* structure with at command parser descriptor */
struct cat_descriptor {
struct cat_command_group* const *cmd_group; /* pointer to array of commands group descriptor */
size_t cmd_group_num; /* number of commands group in array */
uint8_t *buf; /* pointer to working buffer (used to parse command argument) */
size_t buf_size; /* working buffer length */
/* optional unsolicited buffer, if not configured (NULL) */
/* then the buf will be divided into two smaller buffers */
uint8_t *unsolicited_buf; /* pointer to unsolicited working buffer (used to parse command argument) */
size_t unsolicited_buf_size; /* unsolicited working buffer length */
};
/* strcuture with unsolicited command buffered infos */
struct cat_unsolicited_cmd {
struct cat_command const *cmd; /* pointer to commands used to unsolicited event */
cat_cmd_type type; /* type of unsolicited event */
};
/* enum type with unsolicited events fsm state */
typedef enum {
CAT_UNSOLICITED_STATE_IDLE,
CAT_UNSOLICITED_STATE_FORMAT_READ_ARGS,
CAT_UNSOLICITED_STATE_FORMAT_TEST_ARGS,
CAT_UNSOLICITED_STATE_READ_LOOP,
CAT_UNSOLICITED_STATE_TEST_LOOP,
CAT_UNSOLICITED_STATE_FLUSH_IO_WRITE_WAIT,
CAT_UNSOLICITED_STATE_FLUSH_IO_WRITE,
CAT_UNSOLICITED_STATE_AFTER_FLUSH_RESET,
CAT_UNSOLICITED_STATE_AFTER_FLUSH_OK,
CAT_UNSOLICITED_STATE_AFTER_FLUSH_FORMAT_READ_ARGS,
CAT_UNSOLICITED_STATE_AFTER_FLUSH_FORMAT_TEST_ARGS,
} cat_unsolicited_state;
/* enum type with fsm type */
typedef enum {
CAT_FSM_TYPE_ATCMD,
CAT_FSM_TYPE_UNSOLICITED,
CAT_FSM_TYPE__TOTAL_NUM,
} cat_fsm_type;
struct cat_unsolicited_fsm {
cat_unsolicited_state state; /* current unsolicited fsm state */
size_t index; /* index used to iterate over commands and variables */
size_t position; /* position of actually parsed char in arguments string */
struct cat_command const *cmd; /* pointer to current command descriptor */
struct cat_variable const *var; /* pointer to current variable descriptor */
cat_cmd_type cmd_type; /* type of command request */
char const *write_buf; /* working buffer pointer used for asynch writing to io */
int write_state; /* before, data, after flush io write state */
cat_unsolicited_state write_state_after; /* parser state to set after flush io write */
struct cat_unsolicited_cmd unsolicited_cmd_buffer[CAT_UNSOLICITED_CMD_BUFFER_SIZE]; /* buffer with unsolicited commands used to unsolicited event */
size_t unsolicited_cmd_buffer_tail; /* tail index of unsolicited cmd buffer */
size_t unsolicited_cmd_buffer_head; /* head index of unsolicited cmd buffer */
size_t unsolicited_cmd_buffer_items_count; /* number of unsolicited cmd in buffer */
};
/* structure with main at command parser object */
struct cat_object {
struct cat_descriptor const *desc; /* pointer to at command parser descriptor */
struct cat_io_interface const *io; /* pointer to at command parser io interface */
struct cat_mutex_interface const *mutex; /* pointer to at command parser mutex interface */
size_t index; /* index used to iterate over commands and variables */
size_t partial_cntr; /* partial match commands counter */
size_t length; /* length of input command name and command arguments */
size_t position; /* position of actually parsed char in arguments string */
size_t write_size; /* size of parsed buffer hex or buffer string */
size_t commands_num; /* computed total number of registered commands */
struct cat_command const *cmd; /* pointer to current command descriptor */
struct cat_variable const *var; /* pointer to current variable descriptor */
cat_cmd_type cmd_type; /* type of command request */
char current_char; /* current received char from input stream */
cat_state state; /* current fsm state */
bool cr_flag; /* flag for detect <cr> char in input string */
bool hold_state_flag; /* status of hold state (independent from fsm states) */
int hold_exit_status; /* hold exit parameter with status */
char const *write_buf; /* working buffer pointer used for asynch writing to io */
int write_state; /* before, data, after flush io write state */
cat_state write_state_after; /* parser state to set after flush io write */
struct cat_unsolicited_fsm unsolicited_fsm;
};
/**
* Function used to initialize at command parser.
* Initialize starting values of object fields.
*
* @param self pointer to at command parser object to initialize
* @param desc pointer to at command parser descriptor
* @param io pointer to at command parser io low-level layer interface
* @param mutex pointer to at command partes mutex interface
*/
void cat_init(struct cat_object *self, const struct cat_descriptor *desc, const struct cat_io_interface *io, const struct cat_mutex_interface *mutex);
/**
* Function must be called periodically to asynchronoulsy run at command parser.
* Commands handlers will be call from this function context.
*
* @param self pointer to at command parser object
* @return according to cat_return_state enum definitions
*/
cat_status cat_service(struct cat_object *self);
/**
* Function return flag which indicating internal busy state.
* It is used to determine whether external application modules can use shared input / output interfaces functions.
* It is usefull especially in rtos environments.
* If internal parser state is busy by doing some processing then function return 1.
* If the function returns 0, then the external application modules can safely use the input / output interfaces functions shared with the library.
* If the function returns 1, then input / output interface function are used by internal parser functions.
*
* @param self pointer to at command parser object
* @return according to cat_return_state enum definitions
*/
cat_status cat_is_busy(struct cat_object *self);
/**
* Function return flag which indicating parsing hold state.
* If the function returns 0, then the at parsing process is normal.
* If the function returns 1, then the at parsing process is holded.
* To exit from hold state, user have to call cat_hold_exit or return HOLD_EXIT return value in callback.
*
* @param self pointer to at command parser object
* @return according to cat_return_state enum definitions
*/
cat_status cat_is_hold(struct cat_object *self);
/**
* Function return flag which indicating state of internal buffer of unsolicited events.
*
* @param self pointer to at command parser object
* @return CAT_STATUS_OK - buffer is not full, unsolicited event can be buffered
* CAT_STATUS_ERROR_BUFFER_FULL - buffer is full, unsolicited event cannot be buffered
* CAT_STATUS_ERROR_MUTEX_LOCK - cannot lock mutex error
* CAT_STATUS_ERROR_MUTEX_UNLOCK - cannot unlock mutex error
*/
cat_status cat_is_unsolicited_buffer_full(struct cat_object *self);
/**
* Function sends unsolicited event message.
* Command message is buffered inside parser in 1-level deep buffer and processed in cat_service context.
* Only command pointer is buffered, so command struct should be static or global until be fully processed.
*
* @param self pointer to at command parser object
* @param cmd pointer to command structure regarding which unsolicited event applies to
* @param type type of operation (only CAT_CMD_TYPE_READ and CAT_CMD_TYPE_TEST are allowed)
* @return according to cat_return_state enum definitions
*/
cat_status cat_trigger_unsolicited_event(struct cat_object *self, struct cat_command const *cmd, cat_cmd_type type);
/**
* Function sends unsolicited read event message.
* Command message is buffered inside parser in 1-level deep buffer and processed in cat_service context.
* Only command pointer is buffered, so command struct should be static or global until be fully processed.
*
* @param self pointer to at command parser object
* @param cmd pointer to command structure regarding which unsolicited read applies to
* @return according to cat_return_state enum definitions
*/
cat_status cat_trigger_unsolicited_read(struct cat_object *self, struct cat_command const *cmd);
/**
* Function sends unsolicited test event message.
* Command message is buffered inside parser in 1-level deep buffer and processed in cat_service context.
* Only command pointer is buffered, so command struct should be static or global until be fully processed.
*
* @param self pointer to at command parser object
* @param cmd pointer to command structure regarding which unsolicited test applies to
* @return according to cat_return_state enum definitions
*/
cat_status cat_trigger_unsolicited_test(struct cat_object *self, struct cat_command const *cmd);
/**
* Function used to exit from hold state with OK/ERROR response and back to idle state.
*
* @param self pointer to at command parser object
* @param status response status 0 - OK, else ERROR
* @return according to cat_return_state enum definitions
*/
cat_status cat_hold_exit(struct cat_object *self, cat_status status);
/**
* Function used to searching registered command by its name.
*
* @param self pointer to at command parser object
* @param name command name to search
* @return pointer to command object, NULL if command not found
*/
struct cat_command const* cat_search_command_by_name(struct cat_object *self, const char *name);
/**
* Function used to searching registered command group by its name.
*
* @param self pointer to at command parser object
* @param name command group name to search
* @return pointer to command group object, NULL if command group not found
*/
struct cat_command_group const* cat_search_command_group_by_name(struct cat_object *self, const char *name);
/**
* Function used to searching attached variable to command its name.
*
* @param self pointer to at command parser object
* @param cmd pointer to command in which variable will be searched
* @param name variable name to search
* @return pointer to command group object, NULL if command group not found
*/
struct cat_variable const* cat_search_variable_by_name(struct cat_object *self, struct cat_command const *cmd, const char *name);
/**
* Function used to check what command is currently processed.
* Function is not protected by mutex mechanism, due to processed cmd may change after function return.
* This only matters in multithreaded environments, it does not matter for one thread.
*
* @param self pointer to at command parser object
* @param fsm type of internal state machine to check current command
* @return pointer to command which is currently processed, NULL if no command is processed
*/
struct cat_command const* cat_get_processed_command(struct cat_object *self, cat_fsm_type fsm);
/**
* Function return unsolicited event command status.
* Function is not protected by mutex mechanism, due to processed cmd may change after function return.
* This only matters in multithreaded environments, it does not matter for one thread.
*
* @param self pointer to at command parser object
* @param cmd pointer to command in which variable will be searched
* @param type type of unsolicited event
* @return CAT_STATUS_OK - command is not buffered nor processed
* CAT_STATUS_BUSY - command is waiting in buffer or is processed
*/
cat_status cat_is_unsolicited_event_buffered(struct cat_object *self, struct cat_command const *cmd, cat_cmd_type type);
#ifdef __cplusplus
}
#endif
#endif /* CAT_H */

View File

@@ -1,340 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char ack_results[512];
static int8_t var1;
static int8_t var2;
static char const *input_text;
static size_t input_index;
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
return 0;
}
static int cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int cmd_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int cmd_run(const struct cat_command *cmd)
{
return 0;
}
static int var1_write(const struct cat_variable *var, size_t write_size)
{
return 0;
}
static int var2_write(const struct cat_variable *var, size_t write_size)
{
return 0;
}
static int print_cmd_list(const struct cat_command *cmd)
{
return CAT_RETURN_STATE_PRINT_CMD_LIST_OK;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write
}
};
static struct cat_variable vars_ro[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write,
.access = CAT_VAR_ACCESS_READ_ONLY
}
};
static struct cat_variable vars_wo[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write,
.access = CAT_VAR_ACCESS_WRITE_ONLY
}
};
static struct cat_variable vars2[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write
},
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
}
};
static struct cat_variable vars2_ro[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write,
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
}
};
static struct cat_variable vars2_wo[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write,
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
}
};
static struct cat_command cmds[] = {
{
.name = "+V1",
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+V1RO",
.var = vars_ro,
.var_num = sizeof(vars_ro) / sizeof(vars_ro[0])
},
{
.name = "+V1RW",
.var = vars_wo,
.var_num = sizeof(vars_wo) / sizeof(vars_wo[0])
},
{
.name = "+V11",
.var = vars2,
.var_num = sizeof(vars2) / sizeof(vars2[0])
},
{
.name = "+V11RO",
.var = vars2_ro,
.var_num = sizeof(vars2_ro) / sizeof(vars2_ro[0])
},
{
.name = "+V11WO",
.var = vars2_wo,
.var_num = sizeof(vars2_wo) / sizeof(vars2_wo[0])
},
{
.name = "+V2",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+V3",
.write = cmd_write,
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+V4",
.write = cmd_write,
.read = cmd_read,
.test = cmd_test,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+V5",
.write = cmd_write,
.read = cmd_read,
.test = cmd_test,
.run = cmd_run,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+S1",
},
{
.name = "+S2",
.write = cmd_write,
},
{
.name = "+S3",
.write = cmd_write,
.read = cmd_read,
},
{
.name = "+S4",
.write = cmd_write,
.read = cmd_read,
.test = cmd_test,
},
{
.name = "+S5",
.write = cmd_write,
.read = cmd_read,
.test = cmd_test,
.run = cmd_run,
},
{
.name = "+D1",
.write = cmd_write,
.read = cmd_read,
.test = cmd_test,
.run = cmd_run,
.disable = true,
},
{
.name = "+T1",
.write = cmd_write,
.read = cmd_read,
.test = cmd_test,
.run = cmd_run,
.only_test = true,
},
{
.name = "+T2",
.only_test = true,
},
{
.name = "#HELP",
.run = print_cmd_list,
}
};
static char buf[512];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(ack_results, 0, sizeof(ack_results));
}
static void print_raw_text(char *p)
{
while (*p != '\0') {
if (*p == '\n') {
printf("\\n");
} else {
putchar(*p);
}
p++;
}
}
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input("\nAT#HELP\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nAT+V1?\nAT+V1=\nAT+V1=?\n\nAT+V1RO?\nAT+V1RO=?\n\nAT+V1RW=\nAT+V1RW=?\n\nAT+V11?\nAT+V11=\nAT+V11=?\n\nAT+V11RO?\nAT+V11RO=\nAT+V11RO=?\n\nAT+V11WO?\nAT+V11WO=\nAT+V11WO=?\n\nAT+V2?\nAT+V2=\nAT+V2=?\n\nAT+V3?\nAT+V3=\nAT+V3=?\n\nAT+V4?\nAT+V4=\nAT+V4=?\n\nAT+V5\nAT+V5?\nAT+V5=\nAT+V5=?\n\nAT+S2=\n\nAT+S3?\nAT+S3=\n\nAT+S4?\nAT+S4=\nAT+S4=?\n\nAT+S5\nAT+S5?\nAT+S5=\nAT+S5=?\n\nAT+T1=?\n\nAT#HELP\n\nOK\n") == 0);
return 0;
}

View File

@@ -1,229 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char cmd_results[256];
static char var_read_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x, var_u1, var_u2;
static struct cat_object at;
static struct cat_command u_cmds[];
static cat_return_state cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
cat_status s;
strcat(cmd_results, " write:");
strcat(cmd_results, cmd->name);
if (var_x < 2) {
s = cat_trigger_unsolicited_read(&at, &u_cmds[var_x]);
assert(s == CAT_STATUS_OK);
return CAT_RETURN_STATE_HOLD;
}
return CAT_RETURN_STATE_ERROR;
}
static cat_return_state cmd1_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
cat_status s;
strcat(cmd_results, " read1:");
strcat(cmd_results, cmd->name);
if (var_u1 > 0) {
var_u1--;
s = cat_trigger_unsolicited_read(&at, cmd);
assert(s == CAT_STATUS_OK);
return CAT_RETURN_STATE_DATA_OK;
}
return CAT_RETURN_STATE_HOLD_EXIT_OK;
}
static cat_return_state cmd2_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
cat_status s;
strcat(cmd_results, " read2:");
strcat(cmd_results, cmd->name);
if (var_u2 > 0) {
var_u2--;
s = cat_trigger_unsolicited_read(&at, cmd);
assert(s == CAT_STATUS_OK);
return CAT_RETURN_STATE_DATA_OK;
}
cat_hold_exit(&at, CAT_STATUS_OK);
return CAT_RETURN_STATE_DATA_OK;
}
static int var_read(const struct cat_variable *var)
{
strcat(var_read_results, " var_read:");
strcat(var_read_results, var->name);
return 0;
}
static struct cat_variable u_vars[] = {
{
.name = "U1",
.type = CAT_VAR_INT_DEC,
.data = &var_u1,
.data_size = sizeof(var_u1),
.read = var_read
},
{
.name = "U2",
.type = CAT_VAR_INT_DEC,
.data = &var_u2,
.data_size = sizeof(var_u2),
.read = var_read
}
};
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x),
.read = var_read
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static struct cat_command u_cmds[] = {
{
.name = "+U1CMD",
.read = cmd1_read,
.var = &u_vars[0],
.var_num = 1,
},
{
.name = "+U2CMD",
.read = cmd2_read,
.var = &u_vars[1],
.var_num = 1,
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_u1 = 2;
var_u2 = 3;
memset(ack_results, 0, sizeof(ack_results));
memset(cmd_results, 0, sizeof(cmd_results));
memset(var_read_results, 0, sizeof(var_read_results));
}
static const char test_case_1[] = "\nAT+CMD=0\n\nAT+CMD=1\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+U1CMD=2\n\n+U1CMD=1\n\nOK\n\n+U2CMD=3\n\n+U2CMD=2\n\n+U2CMD=1\n\n+U2CMD=0\n\nOK\n") == 0);
assert(strcmp(cmd_results, " write:+CMD read1:+U1CMD read1:+U1CMD read1:+U1CMD write:+CMD read2:+U2CMD read2:+U2CMD read2:+U2CMD read2:+U2CMD") == 0);
assert(strcmp(var_read_results, " var_read:U1 var_read:U1 var_read:U1 var_read:U2 var_read:U2 var_read:U2 var_read:U2") == 0);
return 0;
}

View File

@@ -1,185 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int a_run(const struct cat_command *cmd)
{
strcat(run_results, " A:");
strcat(run_results, cmd->name);
return 0;
}
static int ap_run(const struct cat_command *cmd)
{
strcat(run_results, " AP:");
strcat(run_results, cmd->name);
return 0;
}
static int test_run(const struct cat_command *cmd)
{
strcat(run_results, " +TEST:");
strcat(run_results, cmd->name);
return 0;
}
static struct cat_command cmds[] = {
{
.name = "A",
.run = a_run
},
{
.name = "AP",
.run = ap_run
},
{
.name = "+TEST",
.run = test_run
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static int mutex_ret_lock;
static int mutex_ret_unlock;
static int mutex_lock(void)
{
return mutex_ret_lock;
}
static int mutex_unlock(void)
{
return mutex_ret_unlock;
}
static struct cat_mutex_interface mutex = {
.lock = mutex_lock,
.unlock = mutex_unlock
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
mutex_ret_lock = 0;
mutex_ret_unlock = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nAT\nAT+test\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, &mutex);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nOK\n") == 0);
assert(strcmp(run_results, " +TEST:+TEST") == 0);
mutex_ret_lock = 1;
mutex_ret_unlock = 0;
assert(cat_service(&at) == CAT_STATUS_ERROR_MUTEX_LOCK);
mutex_ret_lock = 0;
mutex_ret_unlock = 1;
assert(cat_service(&at) == CAT_STATUS_ERROR_MUTEX_UNLOCK);
mutex_ret_lock = 1;
mutex_ret_unlock = 0;
assert(cat_is_busy(&at) == CAT_STATUS_ERROR_MUTEX_LOCK);
mutex_ret_lock = 0;
mutex_ret_unlock = 1;
assert(cat_is_busy(&at) == CAT_STATUS_ERROR_MUTEX_UNLOCK);
mutex_ret_lock = 0;
mutex_ret_unlock = 0;
assert(cat_service(&at) == CAT_STATUS_OK);
assert(cat_is_busy(&at) == CAT_STATUS_OK);
return 0;
}

View File

@@ -1,223 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int e_run(const struct cat_command *cmd)
{
strcat(run_results, " E:");
strcat(run_results, cmd->name);
return 0;
}
static int e0_run(const struct cat_command *cmd)
{
strcat(run_results, " E0:");
strcat(run_results, cmd->name);
return 0;
}
static int e1_run(const struct cat_command *cmd)
{
strcat(run_results, " E1:");
strcat(run_results, cmd->name);
return 0;
}
static struct cat_command cmds1[] = {
{
.name = "E",
.run = e_run,
},
{
.name = "E0",
.run = e0_run,
},
{
.name = "E1",
.run = e1_run,
}
};
static struct cat_command cmds2[] = {
{
.name = "E0",
.run = e0_run,
},
{
.name = "E1",
.run = e1_run,
},
{
.name = "E",
.run = e_run,
}
};
static struct cat_command cmds3[] = {
{
.name = "E0",
.run = e0_run,
},
{
.name = "E",
.run = e_run,
},
{
.name = "E1",
.run = e1_run,
}
};
static char buf[128];
static struct cat_command_group cmd_1_group = {
.cmd = cmds1,
.cmd_num = sizeof(cmds1) / sizeof(cmds1[0]),
};
static struct cat_command_group *cmd_1_desc[] = {
&cmd_1_group
};
static struct cat_descriptor desc_1 = {
.cmd_group = cmd_1_desc,
.cmd_group_num = sizeof(cmd_1_desc) / sizeof(cmd_1_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static struct cat_command_group cmd_2_group = {
.cmd = cmds2,
.cmd_num = sizeof(cmds2) / sizeof(cmds2[0]),
};
static struct cat_command_group *cmd_2_desc[] = {
&cmd_2_group
};
static struct cat_descriptor desc_2 = {
.cmd_group = cmd_2_desc,
.cmd_group_num = sizeof(cmd_2_desc) / sizeof(cmd_2_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static struct cat_command_group cmd_3_group = {
.cmd = cmds3,
.cmd_num = sizeof(cmds3) / sizeof(cmds3[0]),
};
static struct cat_command_group *cmd_3_desc[] = {
&cmd_3_group
};
static struct cat_descriptor desc_3 = {
.cmd_group = cmd_3_desc,
.cmd_group_num = sizeof(cmd_3_desc) / sizeof(cmd_3_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nATE\n\nATE0\n\nATE1\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc_1, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nOK\n\nOK\n") == 0);
assert(strcmp(run_results, " E:E E0:E0 E1:E1") == 0);
cat_init(&at, &desc_2, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nOK\n\nOK\n") == 0);
assert(strcmp(run_results, " E:E E0:E0 E1:E1") == 0);
cat_init(&at, &desc_3, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nOK\n\nOK\n") == 0);
assert(strcmp(run_results, " E:E E0:E0 E1:E1") == 0);
return 0;
}

View File

@@ -1,195 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int a_run(const struct cat_command *cmd)
{
strcat(run_results, " A:");
strcat(run_results, cmd->name);
return 0;
}
static int ap_run(const struct cat_command *cmd)
{
strcat(run_results, " AP:");
strcat(run_results, cmd->name);
return 0;
}
static int test_run(const struct cat_command *cmd)
{
strcat(run_results, " +TEST:");
strcat(run_results, cmd->name);
return 0;
}
static struct cat_command cmds[] = {
{
.name = "A",
.run = a_run,
.disable = false,
},
{
.name = "AP",
.run = ap_run,
.disable = false,
},
{
.name = "+TEST",
.run = test_run,
.disable = false,
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nsa\rAT\n\r\nAT\nAT+\n\nATA\r\natap\naaaattttap\na\n\r+test\r\n+testATA\nATAPATAP\n\rAT\rATA\nAT+test\r\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\r\nERROR\r\n\nOK\n\nOK\n\r\nOK\r\n\nOK\n\nERROR\n\nERROR\n\r\nERROR\r\n\nERROR\n\nERROR\n\r\nERROR\r\n\r\nOK\r\n") == 0);
assert(strcmp(run_results, " +TEST:+TEST A:A AP:AP +TEST:+TEST") == 0);
prepare_input("\nAT\n");
while (cat_service(&at) != 0) {};
assert(cat_is_busy(&at) == 0);
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(run_results, "") == 0);
prepare_input("\nAT+te");
while (cat_service(&at) != 0) {};
assert(cat_is_busy(&at) != 0);
assert(strcmp(ack_results, "") == 0);
assert(strcmp(run_results, "") == 0);
prepare_input("st\n");
while (cat_service(&at) != 0) {};
assert(cat_is_busy(&at) == 0);
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(run_results, " +TEST:+TEST") == 0);
struct cat_command *cmd;
cmd = (struct cat_command*)cat_search_command_by_name(&at, "A");
cmd->disable = true;
cmd = (struct cat_command*)cat_search_command_by_name(&at, "+TEST");
cmd->disable = true;
prepare_input("\nATA\n\nATAP\n\nAT+TEST\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(run_results, " AP:AP AP:AP") == 0);
struct cat_command_group *cmd_group;
cmd_group = (struct cat_command_group*)cat_search_command_group_by_name(&at, "standard");
assert(cmd_group == NULL);
cmd_desc[0]->name = "standard";
cmd_group = (struct cat_command_group*)cat_search_command_group_by_name(&at, "standard");
assert(cmd_group == cmd_desc[0]);
cmd_group->disable = true;
prepare_input("\nATA\n\nATAP\n\nAT+TEST\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nERROR\n\nERROR\n") == 0);
assert(strcmp(run_results, "") == 0);
return 0;
}

View File

@@ -1,165 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char read_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int a_run(const struct cat_command *cmd)
{
strcat(run_results, " A_");
strcat(run_results, cmd->name);
return 0;
}
static int a_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(read_results, " A:");
strcat(read_results, cmd->name);
snprintf(data, max_data_size, "%s=A-val", cmd->name);
*data_size = strlen(data);
return 0;
}
static int ap_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(read_results, " AP:");
strcat(read_results, cmd->name);
*data_size = 0;
return 0;
}
static int test_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(read_results, " +TEST:");
strcat(read_results, cmd->name);
return -1;
}
static struct cat_command cmds[] = {
{
.name = "A",
.read = a_read,
.run = a_run
},
{
.name = "AP",
.read = ap_read
},
{
.name = "+TEST",
.read = test_read
},
{
.name = "+EMPTY"
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
memset(read_results, 0, sizeof(read_results));
}
static const char test_case_1[] = "\nAT\r\nAT+\nAT+?\nATA?\r\nATAP\nATAP?\nATAPA?\nAT+TEST?\nAT+te?\nAT+e?\nAT+empTY?\r\nATA\r\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\r\nOK\r\n\nERROR\n\nERROR\n\r\nA=A-val\r\n\r\nOK\r\n\nERROR\n\nAP=\n\nOK\n\nERROR\n\nERROR\n\nERROR\n\nERROR\n\r\nERROR\r\n\r\nOK\r\n") == 0);
assert(strcmp(run_results, " A_A") == 0);
assert(strcmp(read_results, " A:A AP:AP +TEST:+TEST +TEST:+TEST") == 0);
return 0;
}

View File

@@ -1,225 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char ack_results[256];
static int8_t var_int;
static uint8_t var_uint;
static uint8_t var_hex8;
static uint16_t var_hex16;
static uint32_t var_hex32;
static uint8_t var_buf[4];
static char var_string[16];
static char const *input_text;
static size_t input_index;
static int common_cntr;
static uint8_t ctx;
static int cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int cmd2_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
sprintf(data, "%s=test", cmd->name);
*data_size = strlen(data);
return 0;
}
static int common_var_read_handler(const struct cat_variable *var)
{
common_cntr++;
return 0;
}
static void* var_int_data_getter(const struct cat_variable *var, void *context, size_t *data_size)
{
*data_size = sizeof(var_int);
assert(context == &ctx);
return &var_int;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_INT_DEC,
.read = common_var_read_handler,
.data_getter = var_int_data_getter,
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint,
.data_size = sizeof(var_uint),
.read = common_var_read_handler
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex8,
.data_size = sizeof(var_hex8),
.read = common_var_read_handler
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex16,
.data_size = sizeof(var_hex16),
.read = common_var_read_handler
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex32,
.data_size = sizeof(var_hex32),
.read = common_var_read_handler
},
{
.type = CAT_VAR_BUF_HEX,
.data = &var_buf,
.data_size = sizeof(var_buf),
.read = common_var_read_handler
},
{
.type = CAT_VAR_BUF_STRING,
.data = &var_string,
.data_size = sizeof(var_string),
.read = common_var_read_handler
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
.context = &ctx,
},
{
.name = "+TEST",
.read = cmd2_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
.context = &ctx,
},
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_int = -1;
var_uint = 255;
var_hex8 = 0xAA;
var_hex16 = 0x0123;
var_hex32 = 0xFF001234;
var_buf[0] = 0x12;
var_buf[1] = 0x34;
var_buf[2] = 0x56;
var_buf[3] = 0x78;
common_cntr = 0;
sprintf(var_string, "\\\"test\n");
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nAT+SET?\r\n";
static const char test_case_2[] = "\nAT+TEST?\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\r\n+SET=-1,255,0xAA,0x0123,0xFF001234,12345678,\"\\\\\\\"test\\n\"\r\n\r\nOK\r\n") == 0);
assert(common_cntr == 7);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+TEST=test\n\nOK\n") == 0);
assert(common_cntr == 7);
return 0;
}

View File

@@ -1,206 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char cmd_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x;
static struct cat_object at;
static cat_return_state ret;
static bool ret_error;
static cat_return_state cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(cmd_results, " read:");
strcat(cmd_results, cmd->name);
if (cat_is_hold(&at) == CAT_STATUS_HOLD) {
var_x++;
if (var_x > 4) {
ret = (ret_error == false) ? CAT_RETURN_STATE_HOLD_EXIT_OK : CAT_RETURN_STATE_HOLD_EXIT_ERROR;
} else {
if (var_x == 4) {
strcpy(data, "test");
*data_size = strlen(data);
}
ret = CAT_RETURN_STATE_DATA_NEXT;
}
} else {
if ((ret == CAT_RETURN_STATE_DATA_NEXT) || (ret == CAT_RETURN_STATE_NEXT)) {
var_x++;
if (var_x > 2)
ret = CAT_RETURN_STATE_DATA_OK;
} else if (ret == CAT_RETURN_STATE_HOLD) {
cat_trigger_unsolicited_read(&at, cmd);
}
}
return ret;
}
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x)
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
memset(ack_results, 0, sizeof(ack_results));
memset(cmd_results, 0, sizeof(cmd_results));
}
static const char test_case_1[] = "\nAT+CMD?\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
ret = CAT_RETURN_STATE_ERROR;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " read:+CMD") == 0);
ret = CAT_RETURN_STATE_DATA_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=1\n\nOK\n") == 0);
assert(strcmp(cmd_results, " read:+CMD") == 0);
ret = CAT_RETURN_STATE_DATA_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=1\n\n+CMD=2\n\nOK\n") == 0);
assert(strcmp(cmd_results, " read:+CMD read:+CMD") == 0);
ret = CAT_RETURN_STATE_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=2\n\nOK\n") == 0);
assert(strcmp(cmd_results, " read:+CMD read:+CMD") == 0);
ret = CAT_RETURN_STATE_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " read:+CMD") == 0);
ret_error = false;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=1\n\n+CMD=2\n\ntest\n\nOK\n") == 0);
assert(strcmp(cmd_results, " read:+CMD read:+CMD read:+CMD read:+CMD read:+CMD") == 0);
ret_error = true;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=1\n\n+CMD=2\n\ntest\n\nERROR\n") == 0);
assert(strcmp(cmd_results, " read:+CMD read:+CMD read:+CMD read:+CMD read:+CMD") == 0);
return 0;
}

View File

@@ -1,236 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char cmd_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x;
static struct cat_object at;
static cat_return_state ret;
static bool ret_error;
static cat_return_state cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(cmd_results, " read:");
strcat(cmd_results, cmd->name);
var_x++;
if (var_x > 5) {
ret = (ret_error == false) ? CAT_RETURN_STATE_HOLD_EXIT_OK : CAT_RETURN_STATE_HOLD_EXIT_ERROR;
} else {
if (var_x == 5) {
strcpy(data, "test");
*data_size = strlen(data);
}
ret = CAT_RETURN_STATE_DATA_NEXT;
}
return ret;
}
static cat_return_state cmd_run(const struct cat_command *cmd)
{
strcat(cmd_results, " run:");
strcat(cmd_results, cmd->name);
if ((ret == CAT_RETURN_STATE_DATA_NEXT) || (ret == CAT_RETURN_STATE_NEXT)) {
var_x++;
if (var_x > 3)
ret = CAT_RETURN_STATE_DATA_OK;
} else if (ret == CAT_RETURN_STATE_HOLD) {
cat_trigger_unsolicited_read(&at, cmd);
}
return ret;
}
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x)
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.run = cmd_run,
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 2;
memset(ack_results, 0, sizeof(ack_results));
memset(cmd_results, 0, sizeof(cmd_results));
}
static const char test_case_1[] = "\nAT+CMD\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
ret = CAT_RETURN_STATE_ERROR;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " run:+CMD") == 0);
assert(var_x == 2);
ret = CAT_RETURN_STATE_DATA_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " run:+CMD") == 0);
assert(var_x == 2);
ret = CAT_RETURN_STATE_DATA_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " run:+CMD run:+CMD") == 0);
assert(var_x == 4);
ret = CAT_RETURN_STATE_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " run:+CMD run:+CMD") == 0);
assert(var_x == 4);
ret = CAT_RETURN_STATE_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " run:+CMD") == 0);
assert(var_x == 2);
ret_error = false;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=2\n\n+CMD=3\n\ntest\n\nOK\n") == 0);
assert(strcmp(cmd_results, " run:+CMD read:+CMD read:+CMD read:+CMD read:+CMD") == 0);
assert(var_x == 6);
ret_error = true;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=2\n\n+CMD=3\n\ntest\n\nERROR\n") == 0);
assert(strcmp(cmd_results, " run:+CMD read:+CMD read:+CMD read:+CMD read:+CMD") == 0);
assert(var_x == 6);
ret = CAT_RETURN_STATE_HOLD_EXIT_ERROR;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " run:+CMD") == 0);
assert(var_x == 2);
ret = CAT_RETURN_STATE_HOLD_EXIT_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " run:+CMD") == 0);
assert(var_x == 2);
return 0;
}

View File

@@ -1,206 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char cmd_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x;
static struct cat_object at;
static cat_return_state ret;
static bool ret_error;
static cat_return_state cmd_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(cmd_results, " test:");
strcat(cmd_results, cmd->name);
if (cat_is_hold(&at) == CAT_STATUS_HOLD) {
var_x++;
if (var_x > 4) {
ret = (ret_error == false) ? CAT_RETURN_STATE_HOLD_EXIT_OK : CAT_RETURN_STATE_HOLD_EXIT_ERROR;
} else {
if (var_x == 4) {
strcpy(data, "test");
*data_size = strlen(data);
}
ret = CAT_RETURN_STATE_DATA_NEXT;
}
} else {
if ((ret == CAT_RETURN_STATE_DATA_NEXT) || (ret == CAT_RETURN_STATE_NEXT)) {
var_x++;
if (var_x > 2)
ret = CAT_RETURN_STATE_DATA_OK;
} else if (ret == CAT_RETURN_STATE_HOLD) {
cat_trigger_unsolicited_test(&at, cmd);
}
}
return ret;
}
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x)
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.test = cmd_test,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
memset(ack_results, 0, sizeof(ack_results));
memset(cmd_results, 0, sizeof(cmd_results));
}
static const char test_case_1[] = "\nAT+CMD=?\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
ret = CAT_RETURN_STATE_ERROR;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " test:+CMD") == 0);
ret = CAT_RETURN_STATE_DATA_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=<X:INT32[RW]>\n\nOK\n") == 0);
assert(strcmp(cmd_results, " test:+CMD") == 0);
ret = CAT_RETURN_STATE_DATA_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=<X:INT32[RW]>\n\n+CMD=<X:INT32[RW]>\n\nOK\n") == 0);
assert(strcmp(cmd_results, " test:+CMD test:+CMD") == 0);
ret = CAT_RETURN_STATE_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=<X:INT32[RW]>\n\nOK\n") == 0);
assert(strcmp(cmd_results, " test:+CMD test:+CMD") == 0);
ret = CAT_RETURN_STATE_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " test:+CMD") == 0);
ret_error = false;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=<X:INT32[RW]>\n\n+CMD=<X:INT32[RW]>\n\ntest\n\nOK\n") == 0);
assert(strcmp(cmd_results, " test:+CMD test:+CMD test:+CMD test:+CMD test:+CMD") == 0);
ret_error = true;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=<X:INT32[RW]>\n\n+CMD=<X:INT32[RW]>\n\ntest\n\nERROR\n") == 0);
assert(strcmp(cmd_results, " test:+CMD test:+CMD test:+CMD test:+CMD test:+CMD") == 0);
return 0;
}

View File

@@ -1,236 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char cmd_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x;
static struct cat_object at;
static cat_return_state ret;
static bool ret_error;
static cat_return_state cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(cmd_results, " read:");
strcat(cmd_results, cmd->name);
var_x++;
if (var_x > 5) {
ret = (ret_error == false) ? CAT_RETURN_STATE_HOLD_EXIT_OK : CAT_RETURN_STATE_HOLD_EXIT_ERROR;
} else {
if (var_x == 5) {
strcpy(data, "test");
*data_size = strlen(data);
}
ret = CAT_RETURN_STATE_DATA_NEXT;
}
return ret;
}
static cat_return_state cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(cmd_results, " write:");
strcat(cmd_results, cmd->name);
if ((ret == CAT_RETURN_STATE_DATA_NEXT) || (ret == CAT_RETURN_STATE_NEXT)) {
var_x++;
if (var_x > 3)
ret = CAT_RETURN_STATE_DATA_OK;
} else if (ret == CAT_RETURN_STATE_HOLD) {
cat_trigger_unsolicited_read(&at, cmd);
}
return ret;
}
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x)
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.write = cmd_write,
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
memset(ack_results, 0, sizeof(ack_results));
memset(cmd_results, 0, sizeof(cmd_results));
}
static const char test_case_1[] = "\nAT+CMD=2\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
ret = CAT_RETURN_STATE_ERROR;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " write:+CMD") == 0);
assert(var_x == 2);
ret = CAT_RETURN_STATE_DATA_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " write:+CMD") == 0);
assert(var_x == 2);
ret = CAT_RETURN_STATE_DATA_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " write:+CMD write:+CMD") == 0);
assert(var_x == 4);
ret = CAT_RETURN_STATE_NEXT;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " write:+CMD write:+CMD") == 0);
assert(var_x == 4);
ret = CAT_RETURN_STATE_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(strcmp(cmd_results, " write:+CMD") == 0);
assert(var_x == 2);
ret_error = false;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=2\n\n+CMD=3\n\ntest\n\nOK\n") == 0);
assert(strcmp(cmd_results, " write:+CMD read:+CMD read:+CMD read:+CMD read:+CMD") == 0);
assert(var_x == 6);
ret_error = true;
ret = CAT_RETURN_STATE_HOLD;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+CMD=2\n\n+CMD=3\n\ntest\n\nERROR\n") == 0);
assert(strcmp(cmd_results, " write:+CMD read:+CMD read:+CMD read:+CMD read:+CMD") == 0);
assert(var_x == 6);
ret = CAT_RETURN_STATE_HOLD_EXIT_ERROR;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " write:+CMD") == 0);
assert(var_x == 2);
ret = CAT_RETURN_STATE_HOLD_EXIT_OK;
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(strcmp(cmd_results, " write:+CMD") == 0);
assert(var_x == 2);
return 0;
}

View File

@@ -1,157 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int a_run(const struct cat_command *cmd)
{
strcat(run_results, " A:");
strcat(run_results, cmd->name);
return 0;
}
static int ap_run(const struct cat_command *cmd)
{
strcat(run_results, " AP:");
strcat(run_results, cmd->name);
return 0;
}
static int test_run(const struct cat_command *cmd)
{
strcat(run_results, " +TEST:");
strcat(run_results, cmd->name);
return 0;
}
static int force_run(const struct cat_command *cmd)
{
strcat(run_results, " FORCE:");
strcat(run_results, cmd->name);
return -1;
}
static struct cat_command cmds[] = {
{
.name = "A",
.run = a_run
},
{
.name = "AP",
.run = ap_run
},
{
.name = "+TEST",
.run = test_run
},
{
.name = "+EMPTY"
},
{
.name = "FORCE",
.run = force_run,
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nAT\nAT+\nATA\r\nATAP\nATAPA\nAT+TEST\nAT+te\nAT+e\nAT+empTY\naTf\nAtFoRcE\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nERROR\n\r\nOK\r\n\nOK\n\nERROR\n\nOK\n\nOK\n\nERROR\n\nERROR\n\nERROR\n\nERROR\n") == 0);
assert(strcmp(run_results, " A:A AP:AP +TEST:+TEST +TEST:+TEST FORCE:FORCE FORCE:FORCE") == 0);
return 0;
}

View File

@@ -1,224 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static int ap_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int ap_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
return 0;
}
static struct cat_variable vars_ap1[] = {
{
.name = "var_ap1_1"
},
{
.name = "var_ap1_2"
},
{
.name = "var_ap1_3"
}
};
static struct cat_variable vars_apx2[] = {
{
.name = "var_apx2_1"
},
{
.name = "var_apx2_2"
},
{
.name = "var_apx2_3"
}
};
static struct cat_command cmds[] = {
{
.name = "AP1",
.write = ap_write,
.only_test = true,
.var = vars_ap1,
.var_num = sizeof(vars_ap1) / sizeof(vars_ap1[0])
},
{
.name = "AP2",
.read = ap_read,
.only_test = false
},
};
static struct cat_command cmds2[] = {
{
.name = "APX1",
.write = ap_write,
.only_test = true
},
{
.name = "APX2",
.read = ap_read,
.only_test = false,
.var = vars_apx2,
.var_num = sizeof(vars_apx2) / sizeof(vars_apx2[0])
},
};
static char buf[128];
static struct cat_command_group cmd_group1 = {
.name = "std",
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group cmd_group2 = {
.name = "ext",
.cmd = cmds2,
.cmd_num = sizeof(cmds2) / sizeof(cmds2[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group1,
&cmd_group2
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
return 1;
}
static int read_char(char *ch)
{
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
int main(int argc, char **argv)
{
struct cat_object at;
struct cat_command const *cmd;
struct cat_command_group const *cmd_group;
struct cat_variable const *var;
cat_init(&at, &desc, &iface, NULL);
cmd = cat_search_command_by_name(&at, "A");
assert(cmd == NULL);
cmd = cat_search_command_by_name(&at, "AP");
assert(cmd == NULL);
cmd = cat_search_command_by_name(&at, "AP1");
assert(cmd == &cmds[0]);
cmd = cat_search_command_by_name(&at, "AP2");
assert(cmd == &cmds[1]);
cmd = cat_search_command_by_name(&at, "AP3");
assert(cmd == NULL);
cmd = cat_search_command_by_name(&at, "APX1");
assert(cmd == &cmds2[0]);
cmd = cat_search_command_by_name(&at, "APX2");
assert(cmd == &cmds2[1]);
cmd = cat_search_command_by_name(&at, "APX3");
assert(cmd == NULL);
cmd_group = cat_search_command_group_by_name(&at, "std");
assert(cmd_group == cmd_desc[0]);
cmd_group = cat_search_command_group_by_name(&at, "ext");
assert(cmd_group == cmd_desc[1]);
cmd_group = cat_search_command_group_by_name(&at, "not");
assert(cmd_group == NULL);
var = cat_search_variable_by_name(&at, &cmds[0], "v");
assert(var == NULL);
var = cat_search_variable_by_name(&at, &cmds[0], "var_ap");
assert(var == NULL);
var = cat_search_variable_by_name(&at, &cmds[0], "var_apx2");
assert(var == NULL);
var = cat_search_variable_by_name(&at, &cmds[0], "var_ap1_1");
assert(var == &vars_ap1[0]);
var = cat_search_variable_by_name(&at, &cmds[0], "var_ap1_2");
assert(var == &vars_ap1[1]);
var = cat_search_variable_by_name(&at, &cmds[0], "var_ap1_3");
assert(var == &vars_ap1[2]);
var = cat_search_variable_by_name(&at, &cmds[0], "var_ap1_4");
assert(var == NULL);
var = cat_search_variable_by_name(&at, &cmds[1], "var_ap1_1");
assert(var == NULL);
var = cat_search_variable_by_name(&at, &cmds2[1], "var_apx2_1");
assert(var == &vars_apx2[0]);
var = cat_search_variable_by_name(&at, &cmds2[1], "var_apx2_2");
assert(var == &vars_apx2[1]);
var = cat_search_variable_by_name(&at, &cmds2[1], "var_apx2_3");
assert(var == &vars_apx2[2]);
var = cat_search_variable_by_name(&at, &cmds2[1], "var_apx2_4");
assert(var == NULL);
var = cat_search_variable_by_name(&at, &cmds2[0], "var_apx2_1");
assert(var == NULL);
return 0;
}

View File

@@ -1,149 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int print_name(const struct cat_command *cmd)
{
strcat(run_results, cmd->name);
strcat(run_results, " ");
return 0;
}
static struct cat_command cmds[] = {
{
.name = "+TEST",
.run = print_name
},
{
.name = "+TEST_A",
.run = print_name
},
{
.name = "+TEST_B",
.run = print_name
},
{
.name = "+ONE",
.run = print_name
},
{
.name = "+TWO",
.run = print_name
},
};
static char buf[256];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nAT\nAT+\nAT+T\nAT+TE\nAT+TES\nAT+TEST\nAT+TEST_\nAT+TEST_A\nAT+TEST_B\nAT+O\nAT+ON\nAT+ONE\nAT+TW\nAT+TWO\n";
static void print_raw_text(char *p)
{
while (*p != '\0') {
if (*p == '\n') {
printf("\\n");
} else {
putchar(*p);
}
p++;
}
}
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nERROR\n\nERROR\n\nERROR\n\nERROR\n\nOK\n\nERROR\n\nOK\n\nOK\n\nOK\n\nOK\n\nOK\n\nOK\n\nOK\n") == 0);
assert(strcmp(run_results, "+TEST +TEST_A +TEST_B +ONE +ONE +ONE +TWO +TWO ") == 0);
return 0;
}

View File

@@ -1,183 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char test_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int a_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(test_results, " A:");
strcat(test_results, cmd->name);
snprintf(data, max_data_size, "%s=A-val", cmd->name);
*data_size = strlen(data);
return 0;
}
static int ap_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(test_results, " AP:");
strcat(test_results, cmd->name);
*data_size = 0;
return 0;
}
static int ap_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(test_results, " AP_W:");
strcat(test_results, cmd->name);
assert(args_num == 0);
assert(data[0] == 'a');
assert(data_size == 1);
return 0;
}
static int apw_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(test_results, " APW:");
strcat(test_results, cmd->name);
assert(args_num == 0);
assert(data[0] == '?');
assert(data_size == 1);
return 0;
}
static int test_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(test_results, " +TEST:");
strcat(test_results, cmd->name);
return -1;
}
static struct cat_command cmds[] = {
{
.name = "A",
.test = a_test
},
{
.name = "AP",
.test = ap_test,
.write = ap_write
},
{
.name = "APW",
.write = apw_write
},
{
.name = "+TEST",
.test = test_test
},
{
.name = "+EMPTY"
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(ack_results, 0, sizeof(ack_results));
memset(test_results, 0, sizeof(test_results));
}
static const char test_case_1[] = "\nAT\r\nAT\nATAP=?\nATAP=?a\nATAP=a\nATAPW=?\nAT+TEST=?\nATA=?\nAT+EMPTY=?\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\r\nOK\r\n\nOK\n\nAP=\n\nOK\n\nERROR\n\nOK\n\nOK\n\nERROR\n\nA=A-val\n\nOK\n\nERROR\n") == 0);
assert(strcmp(test_results, " AP:AP AP_W:AP APW:APW +TEST:+TEST A:A") == 0);
return 0;
}

View File

@@ -1,459 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char ack_results[256];
static int8_t var_int8;
static int16_t var_int16;
static int32_t var_int32;
static uint8_t var_uint8;
static uint16_t var_uint16;
static uint32_t var_uint32;
static uint8_t var_hex8;
static uint16_t var_hex16;
static uint32_t var_hex32;
static uint8_t var_buf[4];
static char var_string[16];
static char const *input_text;
static size_t input_index;
static int cmd_override_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(data, "\ntest");
*data_size = strlen(data);
return 0;
}
static int cmd_error_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return -1;
}
static int cmd_ok_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcpy(data, "test1");
*data_size = strlen(data);
return 0;
}
static int cmd_ok2_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(data, "test2");
*data_size = strlen(data);
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var_int8,
.data_size = sizeof(var_int8),
.name = "x"
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int16,
.data_size = sizeof(var_int16),
.name = "y"
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int32,
.data_size = sizeof(var_int32)
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint8,
.data_size = sizeof(var_uint8)
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint16,
.data_size = sizeof(var_uint16)
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint32,
.data_size = sizeof(var_uint32)
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex8,
.data_size = sizeof(var_hex8)
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex16,
.data_size = sizeof(var_hex16)
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex32,
.data_size = sizeof(var_hex32)
},
{
.type = CAT_VAR_BUF_HEX,
.data = &var_buf,
.data_size = sizeof(var_buf)
},
{
.type = CAT_VAR_BUF_STRING,
.data = &var_string,
.data_size = sizeof(var_string),
.name = "msg"
}
};
static struct cat_variable vars_ro[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var_int8,
.data_size = sizeof(var_int8),
.name = "x",
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int16,
.data_size = sizeof(var_int16),
.name = "y",
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int32,
.data_size = sizeof(var_int32),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint8,
.data_size = sizeof(var_uint8),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint16,
.data_size = sizeof(var_uint16),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint32,
.data_size = sizeof(var_uint32),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex8,
.data_size = sizeof(var_hex8),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex16,
.data_size = sizeof(var_hex16),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex32,
.data_size = sizeof(var_hex32),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_BUF_HEX,
.data = &var_buf,
.data_size = sizeof(var_buf),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_BUF_STRING,
.data = &var_string,
.data_size = sizeof(var_string),
.name = "msg",
.access = CAT_VAR_ACCESS_READ_ONLY
}
};
static struct cat_variable vars_wo[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var_int8,
.data_size = sizeof(var_int8),
.name = "x",
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int16,
.data_size = sizeof(var_int16),
.name = "y",
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int32,
.data_size = sizeof(var_int32),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint8,
.data_size = sizeof(var_uint8),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint16,
.data_size = sizeof(var_uint16),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint32,
.data_size = sizeof(var_uint32),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex8,
.data_size = sizeof(var_hex8),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex16,
.data_size = sizeof(var_hex16),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex32,
.data_size = sizeof(var_hex32),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_BUF_HEX,
.data = &var_buf,
.data_size = sizeof(var_buf),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_BUF_STRING,
.data = &var_string,
.data_size = sizeof(var_string),
.name = "msg",
.access = CAT_VAR_ACCESS_WRITE_ONLY
}
};
static struct cat_variable vars2[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var_int8,
.data_size = sizeof(var_int8),
.name = "var"
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+SETRO",
.var = vars_ro,
.var_num = sizeof(vars_ro) / sizeof(vars_ro[0])
},
{
.name = "+SETWO",
.var = vars_wo,
.var_num = sizeof(vars_wo) / sizeof(vars_wo[0])
},
{
.name = "+TEST",
.description = "test_desc",
.test = cmd_override_test,
.var = vars2,
.var_num = sizeof(vars2) / sizeof(vars2[0])
},
{
.name = "+TEST2",
.description = "test2_desc",
.var = vars2,
.var_num = sizeof(vars2) / sizeof(vars2[0])
},
{
.name = "+AP",
.test = cmd_error_test,
.var = vars2,
.var_num = sizeof(vars2) / sizeof(vars2[0])
},
{
.name = "+ZZ",
.test = cmd_ok_test,
},
{
.name = "+ZZ2",
.description = "zz2_desc",
.test = cmd_ok_test,
},
{
.name = "+ZZ3",
.description = "zz3_desc",
.test = cmd_ok2_test,
}
};
static char buf[256];
static char unsolicited_buf[256];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
.unsolicited_buf = unsolicited_buf,
.unsolicited_buf_size = sizeof(unsolicited_buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_int8 = -8;
var_int16 = -16;
var_int32 = -32;
var_uint8 = 8;
var_uint8 = 16;
var_uint8 = 32;
var_hex8 = 0x08;
var_hex16 = 0x16;
var_hex32 = 0x32;
var_hex16 = 0x0123;
var_hex32 = 0xFF001234;
var_buf[0] = 0x12;
var_buf[1] = 0x34;
var_buf[2] = 0x56;
var_buf[3] = 0x78;
sprintf(var_string, "TST");
memset(ack_results, 0, sizeof(ack_results));
}
static const char test_case_1[] = "\nAT+SET=?\n";
static const char test_case_1_ro[] = "\nAT+SETRO=?\n";
static const char test_case_1_wo[] = "\nAT+SETWO=?\n";
static const char test_case_2[] = "\nAT+TEST=?\nAT+TEST2=?\r\nAT+AP=?\n";
static const char test_case_3[] = "\nAT+ZZ=?\nAT+ZZ2=?\nAT+ZZ3=?\r\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+SET=<x:INT8[RW]>,<y:INT16[RW]>,<INT32[RW]>,<UINT8[RW]>,<UINT16[RW]>,<UINT32[RW]>,<HEX8[RW]>,<HEX16[RW]>,<HEX32[RW]>,<HEXBUF[RW]>,<msg:STRING[RW]>\n\nOK\n") == 0);
prepare_input(test_case_1_ro);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+SETRO=<x:INT8[RO]>,<y:INT16[RO]>,<INT32[RO]>,<UINT8[RO]>,<UINT16[RO]>,<UINT32[RO]>,<HEX8[RO]>,<HEX16[RO]>,<HEX32[RO]>,<HEXBUF[RO]>,<msg:STRING[RO]>\n\nOK\n") == 0);
prepare_input(test_case_1_wo);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+SETWO=<x:INT8[WO]>,<y:INT16[WO]>,<INT32[WO]>,<UINT8[WO]>,<UINT16[WO]>,<UINT32[WO]>,<HEX8[WO]>,<HEX16[WO]>,<HEX32[WO]>,<HEXBUF[WO]>,<msg:STRING[WO]>\n\nOK\n") == 0);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+TEST=<var:INT8[RW]>\ntest_desc\ntest\n\nOK\n\r\n+TEST2=<var:INT8[RW]>\r\ntest2_desc\r\n\r\nOK\r\n\nERROR\n") == 0);
prepare_input(test_case_3);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\ntest1\n\nOK\n\ntest1\n\nOK\n\r\n+ZZ3=\r\nzz3_desctest2\r\n\r\nOK\r\n") == 0);
return 0;
}

View File

@@ -1,173 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char cmd_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int ap_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(cmd_results, " test:");
strcat(cmd_results, cmd->name);
strcpy(data, "ap_test");
*data_size = strlen(data);
return 0;
}
static int ap_run(const struct cat_command *cmd)
{
strcat(cmd_results, " run:");
strcat(cmd_results, cmd->name);
return 0;
}
static int ap_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
strcat(cmd_results, " read:");
strcat(cmd_results, cmd->name);
strcpy(data, "ap_read");
*data_size = strlen(data);
return 0;
}
static int ap_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(cmd_results, " write:");
strcat(cmd_results, cmd->name);
assert(strcmp(data, "1") == 0);
return 0;
}
static struct cat_command cmds[] = {
{
.name = "AP1",
.test = ap_test,
.write = ap_write,
.read = ap_read,
.run = ap_run,
.only_test = true
},
{
.name = "AP2",
.test = ap_test,
.write = ap_write,
.read = ap_read,
.run = ap_run,
.only_test = false
},
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(ack_results, 0, sizeof(ack_results));
memset(cmd_results, 0, sizeof(cmd_results));
}
static const char test_case_1[] = "\nATAP1=?\n\nATAP1?\n\nATAP1=1\n\nATAP1\n";
static const char test_case_2[] = "\nATAP2=?\n\nATAP2?\n\nATAP2=1\n\nATAP2\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nap_test\n\nOK\n\nERROR\n\nERROR\n\nERROR\n") == 0);
assert(strcmp(cmd_results, " test:AP1") == 0);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nap_test\n\nOK\n\nap_read\n\nOK\n\nOK\n\nOK\n") == 0);
assert(strcmp(cmd_results, " test:AP2 read:AP2 write:AP2 run:AP2") == 0);
return 0;
}

View File

@@ -1,203 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char read_results[256];
static char var_read_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x, var_u1, var_u2;
static struct cat_object at;
static struct cat_command u_cmds[];
static cat_return_state cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
cat_status s;
strcat(read_results, " read:");
strcat(read_results, cmd->name);
if (strcmp(cmd->name, "+CMD") == 0) {
s = cat_trigger_unsolicited_read(&at, &u_cmds[1]);
assert(s == CAT_STATUS_OK);
}
return CAT_RETURN_STATE_DATA_OK;
}
static int var_read(const struct cat_variable *var)
{
strcat(var_read_results, " var_read:");
strcat(var_read_results, var->name);
return 0;
}
static struct cat_variable u_vars[] = {
{
.name = "U1",
.type = CAT_VAR_INT_DEC,
.data = &var_u1,
.data_size = sizeof(var_u1),
.read = var_read
},
{
.name = "U2",
.type = CAT_VAR_INT_DEC,
.data = &var_u2,
.data_size = sizeof(var_u2),
.read = var_read
}
};
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x),
.read = var_read
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static struct cat_command u_cmds[] = {
{
.name = "+U1CMD",
.read = cmd_read,
.var = &u_vars[0],
.var_num = 1,
},
{
.name = "+U2CMD",
.read = cmd_read,
.var = &u_vars[1],
.var_num = 1,
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
var_u1 = 2;
var_u2 = 3;
memset(ack_results, 0, sizeof(ack_results));
memset(read_results, 0, sizeof(read_results));
memset(var_read_results, 0, sizeof(var_read_results));
}
static const char test_case_1[] = "\nAT+CMD?\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_OK);
s = cat_trigger_unsolicited_event(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_ERROR_BUFFER_FULL);
s = cat_trigger_unsolicited_read(&at, &u_cmds[1]);
assert(s == CAT_STATUS_ERROR_BUFFER_FULL);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+U1CMD=2\n\n+CMD=1\n\n+U2CMD=3\n\nOK\n") == 0);
assert(strcmp(read_results, " read:+U1CMD read:+CMD read:+U2CMD") == 0);
assert(strcmp(var_read_results, " var_read:U1 var_read:X var_read:U2") == 0);
return 0;
}

View File

@@ -1,238 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char read_results[256];
static char var_read_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x, var_u1, var_u2;
static struct cat_object at;
static struct cat_command u_cmds[];
static cat_return_state cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
cat_status s;
strcat(read_results, " read:");
strcat(read_results, cmd->name);
if (strcmp(cmd->name, "+CMD") == 0) {
s = cat_trigger_unsolicited_read(&at, &u_cmds[1]);
assert(s == CAT_STATUS_OK);
}
return CAT_RETURN_STATE_DATA_OK;
}
static int var_read(const struct cat_variable *var)
{
strcat(var_read_results, " var_read:");
strcat(var_read_results, var->name);
return 0;
}
static struct cat_variable u_vars[] = {
{
.name = "U1",
.type = CAT_VAR_INT_DEC,
.data = &var_u1,
.data_size = sizeof(var_u1),
.read = var_read
},
{
.name = "U2",
.type = CAT_VAR_INT_DEC,
.data = &var_u2,
.data_size = sizeof(var_u2),
.read = var_read
}
};
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x),
.read = var_read
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static struct cat_command u_cmds[] = {
{
.name = "+U1CMD",
.read = cmd_read,
.var = &u_vars[0],
.var_num = 1,
},
{
.name = "+U2CMD",
.read = cmd_read,
.var = &u_vars[1],
.var_num = 1,
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
var_u1 = 2;
var_u2 = 3;
memset(ack_results, 0, sizeof(ack_results));
memset(read_results, 0, sizeof(read_results));
memset(var_read_results, 0, sizeof(var_read_results));
}
static const char test_case_1[] = "\nAT+CMD?\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_OK);
s = cat_trigger_unsolicited_event(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_TEST);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_OK);
s = cat_trigger_unsolicited_event(&at, &u_cmds[1], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_TEST);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[1], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[1], CAT_CMD_TYPE_TEST);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[1], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_ERROR_BUFFER_FULL);
s = cat_trigger_unsolicited_read(&at, &u_cmds[1]);
assert(s == CAT_STATUS_ERROR_BUFFER_FULL);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_TEST);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[1], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_BUSY);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+U1CMD=2\n\n+CMD=1\n\n+U2CMD=3\n\nOK\n\n+U2CMD=3\n") == 0);
assert(strcmp(read_results, " read:+U1CMD read:+CMD read:+U2CMD read:+U2CMD") == 0);
assert(strcmp(var_read_results, " var_read:U1 var_read:X var_read:U2 var_read:U2") == 0);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_OK);
return 0;
}

View File

@@ -1,211 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char read_results[256];
static char var_read_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x, var_u1;
static struct cat_object at;
static struct cat_command u_cmds[];
static cat_return_state cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
cat_status s;
strcat(read_results, " read:");
strcat(read_results, cmd->name);
return CAT_RETURN_STATE_DATA_OK;
}
static int var_read(const struct cat_variable *var)
{
strcat(var_read_results, " var_read:");
strcat(var_read_results, var->name);
return 0;
}
static struct cat_variable u_vars[] = {
{
.name = "U1",
.type = CAT_VAR_INT_DEC,
.data = &var_u1,
.data_size = sizeof(var_u1),
.read = var_read
}
};
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x),
.read = var_read
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.read = cmd_read,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static struct cat_command u_cmds[] = {
{
.name = "+UCMD",
.read = cmd_read,
.var = u_vars,
.var_num = sizeof(u_vars) / sizeof(u_vars[0]),
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
var_u1 = 2;
memset(ack_results, 0, sizeof(ack_results));
memset(read_results, 0, sizeof(read_results));
memset(var_read_results, 0, sizeof(var_read_results));
}
static const char test_case_1[] = "\nAT+CMD?\n";
int main(int argc, char **argv)
{
cat_status s;
int events = 4;
struct cat_command const *cmd;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
cmd = cat_get_processed_command(&at, CAT_FSM_TYPE_ATCMD);
assert(cmd == NULL);
cmd = cat_get_processed_command(&at, CAT_FSM_TYPE_UNSOLICITED);
assert(cmd == NULL);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_OK);
while (events > 0) {
s = cat_is_unsolicited_buffer_full(&at);
cmd = cat_get_processed_command(&at, CAT_FSM_TYPE_UNSOLICITED);
if ((s == CAT_STATUS_OK) && (cmd == NULL)) {
var_u1 = events;
s = cat_trigger_unsolicited_event(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_OK);
events--;
} else {
assert(cmd == &u_cmds[0]);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_READ);
assert(s == CAT_STATUS_BUSY);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_TEST);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_event_buffered(&at, &u_cmds[0], CAT_CMD_TYPE_NONE);
assert(s == CAT_STATUS_BUSY);
}
s = cat_service(&at);
assert(s == CAT_STATUS_BUSY);
}
while (cat_service(&at) != 0) {};
cmd = cat_get_processed_command(&at, CAT_FSM_TYPE_ATCMD);
assert(cmd == NULL);
cmd = cat_get_processed_command(&at, CAT_FSM_TYPE_UNSOLICITED);
assert(cmd == NULL);
assert(strcmp(ack_results, "\n+UCMD=4\n\n+CMD=1\n\n+UCMD=3\n\nOK\n\n+UCMD=2\n\n+UCMD=1\n") == 0);
assert(strcmp(read_results, " read:+UCMD read:+CMD read:+UCMD read:+UCMD read:+UCMD") == 0);
assert(strcmp(var_read_results, " var_read:U1 var_read:X var_read:U1 var_read:U1 var_read:U1") == 0);
return 0;
}

View File

@@ -1,189 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char read_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int var_x, var_u1, var_u2;
static struct cat_object at;
static struct cat_command u_cmds[];
static cat_return_state cmd_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
cat_status s;
strcat(read_results, " test:");
strcat(read_results, cmd->name);
if (strcmp(cmd->name, "+CMD") == 0) {
s = cat_trigger_unsolicited_test(&at, &u_cmds[1]);
assert(s == CAT_STATUS_OK);
}
return CAT_RETURN_STATE_DATA_OK;
}
static struct cat_variable u_vars[] = {
{
.name = "U1",
.type = CAT_VAR_INT_DEC,
.data = &var_u1,
.data_size = sizeof(var_u1)
},
{
.name = "U2",
.type = CAT_VAR_INT_DEC,
.data = &var_u2,
.data_size = sizeof(var_u2)
}
};
static struct cat_variable vars[] = {
{
.name = "X",
.type = CAT_VAR_INT_DEC,
.data = &var_x,
.data_size = sizeof(var_x)
}
};
static struct cat_command cmds[] = {
{
.name = "+CMD",
.test = cmd_test,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
}
};
static struct cat_command u_cmds[] = {
{
.name = "+U1CMD",
.test = cmd_test,
.var = &u_vars[0],
.var_num = 1,
},
{
.name = "+U2CMD",
.test = cmd_test,
.var = &u_vars[1],
.var_num = 1,
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var_x = 1;
var_u1 = 2;
var_u2 = 3;
memset(ack_results, 0, sizeof(ack_results));
memset(read_results, 0, sizeof(read_results));
}
static const char test_case_1[] = "\nAT+CMD=?\n";
int main(int argc, char **argv)
{
cat_status s;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_OK);
s = cat_trigger_unsolicited_event(&at, &u_cmds[0], CAT_CMD_TYPE_TEST);
assert(s == CAT_STATUS_OK);
s = cat_is_unsolicited_buffer_full(&at);
assert(s == CAT_STATUS_ERROR_BUFFER_FULL);
s = cat_trigger_unsolicited_test(&at, &u_cmds[1]);
assert(s == CAT_STATUS_ERROR_BUFFER_FULL);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+U1CMD=<U1:INT32[RW]>\n\n+CMD=<X:INT32[RW]>\n\n+U2CMD=<U2:INT32[RW]>\n\nOK\n") == 0);
assert(strcmp(read_results, " test:+U1CMD test:+CMD test:+U2CMD") == 0);
return 0;
}

View File

@@ -1,545 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char ack_results[256];
static int8_t var1;
static int8_t var2;
static int8_t var3;
static char const *input_text;
static size_t input_index;
static int var2_write_cntr;
static int var3_read_cntr;
static int8_t var_int8;
static int16_t var_int16;
static int32_t var_int32;
static uint8_t var_uint8;
static uint16_t var_uint16;
static uint32_t var_uint32;
static uint8_t var_hex8;
static uint16_t var_hex16;
static uint32_t var_hex32;
static uint8_t var_buf[4];
static char var_string[16];
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
return 0;
}
static int cmd_read(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int cmd_test(const struct cat_command *cmd, uint8_t *data, size_t *data_size, const size_t max_data_size)
{
return 0;
}
static int cmd_run(const struct cat_command *cmd)
{
return 0;
}
static int var1_write(const struct cat_variable *var, size_t write_size)
{
return 0;
}
static int var1_read(const struct cat_variable *var)
{
return 0;
}
static int var2_write(const struct cat_variable *var, size_t write_size)
{
var2_write_cntr++;
return 0;
}
static int var2_read(const struct cat_variable *var)
{
return 0;
}
static int var3_write(const struct cat_variable *var, size_t write_size)
{
return 0;
}
static int var3_read(const struct cat_variable *var)
{
var3_read_cntr++;
return 0;
}
static int print_cmd_list(const struct cat_command *cmd)
{
return CAT_RETURN_STATE_PRINT_CMD_LIST_OK;
}
static struct cat_variable vars_ro[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write,
.read = var2_read,
.access = CAT_VAR_ACCESS_READ_ONLY
}
};
static struct cat_variable vars_wo[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var3,
.data_size = sizeof(var3),
.write = var3_write,
.read = var3_read,
.access = CAT_VAR_ACCESS_WRITE_ONLY
}
};
static struct cat_variable vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write,
.read = var1_read,
.access = CAT_VAR_ACCESS_READ_WRITE
},
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write,
.read = var2_read,
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var3,
.data_size = sizeof(var3),
.write = var3_write,
.read = var3_read,
.access = CAT_VAR_ACCESS_WRITE_ONLY
}
};
static struct cat_variable vars_misc_ro[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.access = CAT_VAR_ACCESS_READ_WRITE
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int8,
.data_size = sizeof(var_int8),
.name = "x",
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int16,
.data_size = sizeof(var_int16),
.name = "y",
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int32,
.data_size = sizeof(var_int32),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint8,
.data_size = sizeof(var_uint8),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint16,
.data_size = sizeof(var_uint16),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint32,
.data_size = sizeof(var_uint32),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex8,
.data_size = sizeof(var_hex8),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex16,
.data_size = sizeof(var_hex16),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex32,
.data_size = sizeof(var_hex32),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_BUF_HEX,
.data = &var_buf,
.data_size = sizeof(var_buf),
.access = CAT_VAR_ACCESS_READ_ONLY
},
{
.type = CAT_VAR_BUF_STRING,
.data = &var_string,
.data_size = sizeof(var_string),
.name = "msg",
.access = CAT_VAR_ACCESS_READ_ONLY
}
};
static struct cat_variable vars_misc_wo[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.access = CAT_VAR_ACCESS_READ_WRITE
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int8,
.data_size = sizeof(var_int8),
.name = "x",
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int16,
.data_size = sizeof(var_int16),
.name = "y",
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_INT_DEC,
.data = &var_int32,
.data_size = sizeof(var_int32),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint8,
.data_size = sizeof(var_uint8),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint16,
.data_size = sizeof(var_uint16),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var_uint32,
.data_size = sizeof(var_uint32),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex8,
.data_size = sizeof(var_hex8),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex16,
.data_size = sizeof(var_hex16),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var_hex32,
.data_size = sizeof(var_hex32),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_BUF_HEX,
.data = &var_buf,
.data_size = sizeof(var_buf),
.access = CAT_VAR_ACCESS_WRITE_ONLY
},
{
.type = CAT_VAR_BUF_STRING,
.data = &var_string,
.data_size = sizeof(var_string),
.name = "msg",
.access = CAT_VAR_ACCESS_WRITE_ONLY
}
};
static struct cat_command cmds[] = {
{
.name = "+VRW",
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+VRO",
.var = vars_ro,
.var_num = sizeof(vars_ro) / sizeof(vars_ro[0])
},
{
.name = "+VWO",
.var = vars_wo,
.var_num = sizeof(vars_wo) / sizeof(vars_wo[0])
},
{
.name = "+MRO",
.var = vars_misc_ro,
.var_num = sizeof(vars_misc_ro) / sizeof(vars_misc_ro[0])
},
{
.name = "+MWO",
.var = vars_misc_wo,
.var_num = sizeof(vars_misc_wo) / sizeof(vars_misc_wo[0])
},
{
.name = "#HELP",
.run = print_cmd_list,
}
};
static char buf[256];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(ack_results, 0, sizeof(ack_results));
}
static void print_raw_text(char *p)
{
while (*p != '\0') {
if (*p == '\n') {
printf("\\n");
} else {
putchar(*p);
}
p++;
}
}
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input("\nAT#HELP\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nAT+VRW?\nAT+VRW=\nAT+VRW=?\n\nAT+VRO?\nAT+VRO=?\n\nAT+VWO=\nAT+VWO=?\n\nAT+MRO?\nAT+MRO=\nAT+MRO=?\n\nAT+MWO?\nAT+MWO=\nAT+MWO=?\n\nAT#HELP\n\nOK\n") == 0);
prepare_input("\nAT+VRW=?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+VRW=<INT8[RW]>,<INT8[RO]>,<INT8[WO]>\n\nOK\n") == 0);
prepare_input("\nAT+VRO=?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+VRO=<INT8[RO]>\n\nOK\n") == 0);
prepare_input("\nAT+VWO=?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+VWO=<INT8[WO]>\n\nOK\n") == 0);
var2 = 1;
prepare_input("\nAT+VRO=1\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(var2 == 1);
var3 = 3;
prepare_input("\nAT+VWO?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n") == 0);
assert(var3 == 3);
var1 = -1;
var2 = -2;
var3 = -3;
var2_write_cntr = 0;
var3_read_cntr = 0;
prepare_input("\nAT+VRW?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+VRW=-1,-2,0\n\nOK\n") == 0);
assert(var2_write_cntr == 0);
assert(var3_read_cntr == 1);
prepare_input("\nAT+VRW=1,2,3\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(var2_write_cntr == 1);
assert(var3_read_cntr == 1);
assert(var1 == 1);
assert(var2 == -2);
assert(var3 == 3);
var1 = 100;
var_int8 = 1;
var_int16 = 2;
var_int32 = 3;
var_uint8 = 4;
var_uint16 = 5;
var_uint32 = 6;
var_hex8 = 7;
var_hex16 = 8;
var_hex32 = 9;
var_buf[0] = 0x10;
var_buf[1] = 0x11;
var_buf[2] = 0x12;
var_buf[3] = 0x13;
strcpy(var_string, "test_string");
prepare_input("\nAT+MWO?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+MWO=100,0,0,0,0,0,0,0x00,0x0000,0x00000000,00000000,\"\"\n\nOK\n") == 0);
prepare_input("\nAT+MWO=1,2,3,4,5,6,7,0x08,0x0009,0x0000000A,01020304,\"abc\"\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(var1 == 1);
assert(var_int8 == 2);
assert(var_int16 == 3);
assert(var_int32 == 4);
assert(var_uint8 == 5);
assert(var_uint16 == 6);
assert(var_uint32 == 7);
assert(var_hex8 == 8);
assert(var_hex16 == 9);
assert(var_hex32 == 10);
assert(var_buf[0] == 0x01);
assert(var_buf[1] == 0x02);
assert(var_buf[2] == 0x03);
assert(var_buf[3] == 0x04);
assert(strcmp(var_string, "abc") == 0);
prepare_input("\nAT+MRO?\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\n+MRO=1,2,3,4,5,6,7,0x08,0x0009,0x0000000A,01020304,\"abc\"\n\nOK\n") == 0);
prepare_input("\nAT+MRO=2,0,0,0,0,0,0,0x00,0x0000,0x00000000,00000000,\"cba\"\n");
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n") == 0);
assert(var1 == 2);
assert(var_int8 == 2);
assert(var_int16 == 3);
assert(var_int32 == 4);
assert(var_uint8 == 5);
assert(var_uint16 == 6);
assert(var_uint32 == 7);
assert(var_hex8 == 8);
assert(var_hex16 == 9);
assert(var_hex32 == 10);
assert(var_buf[0] == 0x01);
assert(var_buf[1] == 0x02);
assert(var_buf[2] == 0x03);
assert(var_buf[3] == 0x04);
assert(strcmp(var_string, "abc") == 0);
return 0;
}

View File

@@ -1,166 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char run_results[256];
static char write_results[256];
static char ack_results[256];
static char const *input_text;
static size_t input_index;
static int a_run(const struct cat_command *cmd)
{
strcat(run_results, " A_");
strcat(run_results, cmd->name);
return 0;
}
static int a_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " A:");
strncat(write_results, data, data_size);
assert(args_num == 0);
return 0;
}
static int ap_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " AP:");
strncat(write_results, data, data_size);
assert(args_num == 0);
return 0;
}
static int test_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " +TEST:");
strncat(write_results, data, data_size);
assert(args_num == 0);
return -1;
}
static struct cat_command cmds[] = {
{
.name = "A",
.write = a_write,
.run = a_run
},
{
.name = "AP",
.write = ap_write
},
{
.name = "+TEST",
.write = test_write
},
{
.name = "+EMPTY"
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(run_results, 0, sizeof(run_results));
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT\nAT+\nAT+?\nATA=123\r\nATA=\nATAP?\nATAP=11\r22\r\nAT+TEST=456\nAT+te=789\nAT+e=1\nAT+empTY=2\r\nATA\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nERROR\n\nERROR\n\r\nOK\r\n\nOK\n\nERROR\n\r\nOK\r\n\nERROR\n\nERROR\n\nERROR\n\r\nERROR\r\n\nOK\n") == 0);
assert(strcmp(run_results, " A_A") == 0);
assert(strcmp(write_results, " A:123 A: AP:1122 +TEST:456 +TEST:789") == 0);
return 0;
}

View File

@@ -1,174 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char write_results[256];
static char ack_results[256];
static uint8_t var[4];
static size_t var_write_size[4];
static int var_write_size_index;
static char const *input_text;
static size_t input_index;
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " CMD:");
strncat(write_results, data, data_size);
return 0;
}
static int var_write(const struct cat_variable *var, size_t write_size)
{
var_write_size[var_write_size_index++] = write_size;
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_BUF_HEX,
.data = var,
.data_size = sizeof(var),
.write = var_write
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(var, 0, sizeof(var));
memset(var_write_size, 0, sizeof(var_write_size));
var_write_size_index = 0;
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT+SET=0\nAT+SET=aa\nAT+SET=001\nAT+SET=12345678\nAT+SET=ffAA\n";
static const char test_case_2[] = "\nAT+SET=0x11\nAT+SET=11\nAT+SET=-1\nAT+SET=87654321\nAT+SET=0001\nAT+SET=1122334455\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nERROR\n\nOK\n\nOK\n") == 0);
assert(strcmp(write_results, " CMD:aa CMD:12345678 CMD:ffAA") == 0);
assert(var[0] == 0xFF);
assert(var[1] == 0xAA);
assert(var[2] == 0x56);
assert(var[3] == 0x78);
assert(var_write_size[0] == 1);
assert(var_write_size[1] == 4);
assert(var_write_size[2] == 2);
assert(var_write_size[3] == 0);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nERROR\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:11 CMD:87654321 CMD:0001") == 0);
assert(var[0] == 0x11);
assert(var[1] == 0x22);
assert(var[2] == 0x33);
assert(var[3] == 0x44);
assert(var_write_size[0] == 1);
assert(var_write_size[1] == 4);
assert(var_write_size[2] == 2);
assert(var_write_size[3] == 0);
return 0;
}

View File

@@ -1,212 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char write_results[256];
static char ack_results[256];
static uint8_t var1, var1b;
static uint16_t var2, var2b;
static uint32_t var3, var3b;
static char const *input_text;
static size_t input_index;
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " CMD:");
strncat(write_results, data, data_size);
return 0;
}
static int var1_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 1);
var1b = *(uint8_t*)(var->data);
return 0;
}
static int var2_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 2);
var2b = *(uint16_t*)(var->data);
return 0;
}
static int var3_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 4);
var3b = *(uint32_t*)(var->data);
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_NUM_HEX,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
},
{
.type = CAT_VAR_NUM_HEX,
.data = &var3,
.data_size = sizeof(var3),
.write = var3_write
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var1 = 1;
var2 = 2;
var3 = 3;
var1b = 10;
var2b = 20;
var3b = 30;
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT+SET=0\nAT+SET=0x0\nAT+SET=0x01\nAT+SET=0x0ff\nAT+SET=0x100\n";
static const char test_case_2[] = "\nAT+SET=0x,0x00\nAT+SET=0x1,0x00\nAT+SET=0x2,0xFFf\nAT+SET=0x3,0xFFFF\nAT+SET=0x4,0xFFFFF\n";
static const char test_case_3[] = "\nAT+SET=0x0,0x0,0\nAT+SET=0x0,0x0,0x0000000000000\nAT+SET=0x0,0x0,0x1\nAT+SET=0x0,0x0,0xffffFFFF\nAT+SET=0x10,0x20,0x100000000\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0x0 CMD:0x01 CMD:0x0ff") == 0);
assert(var1 == 255);
assert(var1b == var1);
assert(var2 == 2);
assert(var2b == 20);
assert(var3 == 3);
assert(var3b == 30);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0x1,0x00 CMD:0x2,0xFFf CMD:0x3,0xFFFF") == 0);
assert(var1 == 4);
assert(var1b == var1);
assert(var2 == 0xFFFF);
assert(var2b == var2);
assert(var3 == 3);
assert(var3b == 30);
prepare_input(test_case_3);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0x0,0x0,0x0000000000000 CMD:0x0,0x0,0x1 CMD:0x0,0x0,0xffffFFFF") == 0);
assert(var1 == 0x10);
assert(var1b == var1);
assert(var2 == 0x20);
assert(var2b == var2);
assert(var3 == 0xFFFFFFFF);
assert(var3b == var3);
return 0;
}

View File

@@ -1,212 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char write_results[256];
static char ack_results[256];
static int8_t var1, var1b;
static int16_t var2, var2b;
static int32_t var3, var3b;
static char const *input_text;
static size_t input_index;
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " CMD:");
strncat(write_results, data, data_size);
return 0;
}
static int var1_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 1);
var1b = *(int8_t*)(var->data);
return 0;
}
static int var2_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 2);
var2b = *(int16_t*)(var->data);
return 0;
}
static int var3_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 4);
var3b = *(int32_t*)(var->data);
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write
},
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
},
{
.type = CAT_VAR_INT_DEC,
.data = &var3,
.data_size = sizeof(var3),
.write = var3_write
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf),
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var1 = 1;
var2 = 2;
var3 = 3;
var1b = -1;
var2b = -2;
var3b = -3;
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT+SET=-128\nAT+SET=-129\nAT+SET=127\nAT+SET=128\n";
static const char test_case_2[] = "\nAT+SET=-128,-32768\nAT+SET=-128,-40000\nAT+SET=-128,32767\nAT+SET=-100,40000\n";
static const char test_case_3[] = "\nAT+SET=0,0,-2147483648\nAT+SET=0,0,-2147483649\nAT+SET=1,1,2147483647\nAT+SET=2,2,2147483648\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nERROR\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:-128 CMD:127") == 0);
assert(var1 == 127);
assert(var1b == var1);
assert(var2 == 2);
assert(var2b == -2);
assert(var3 == 3);
assert(var3b == -3);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nERROR\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:-128,-32768 CMD:-128,32767") == 0);
assert(var1 == -100);
assert(var1b == var1);
assert(var2 == 32767);
assert(var2b == var2);
assert(var3 == 3);
assert(var3b == -3);
prepare_input(test_case_3);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nERROR\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0,0,-2147483648 CMD:1,1,2147483647") == 0);
assert(var1 == 2);
assert(var1b == var1);
assert(var2 == 2);
assert(var2b == var2);
assert(var3 == 2147483647);
assert(var3b == var3);
return 0;
}

View File

@@ -1,234 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char write_results[256];
static char ack_results[256];
static int8_t var1, var2, var3;
static int8_t var1b, var2b, var3b;
static char const *input_text;
static size_t input_index;
static int cmd_write1(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
char tmp[32];
sprintf(tmp, " CMD1_%ld:", args_num);
strcat(write_results, tmp);
strncat(write_results, data, data_size);
return 0;
}
static int cmd_write3(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
char tmp[32];
sprintf(tmp, " CMD3_%ld:", args_num);
strcat(write_results, tmp);
strncat(write_results, data, data_size);
return 0;
}
static int var1_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 1);
var1b = *(int8_t*)(var->data);
return 0;
}
static int var2_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 1);
var2b = *(int8_t*)(var->data);
return 0;
}
static int var3_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 1);
var3b = *(int8_t*)(var->data);
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_INT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write
},
{
.type = CAT_VAR_INT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
},
{
.type = CAT_VAR_INT_DEC,
.data = &var3,
.data_size = sizeof(var3),
.write = var3_write
}
};
static struct cat_command cmds[] = {
{
.name = "+SET1",
.write = cmd_write1,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+SET3",
.write = cmd_write3,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
},
{
.name = "+SETALL",
.write = cmd_write3,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0]),
.need_all_vars = true
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var1 = 1;
var2 = 2;
var3 = 3;
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT+SET=-10,-20,-30\r\nAT+SET1=-10,-20,-30\r\nAT+SET1=-1\r\n";
static const char test_case_2[] = "\nAT+SET3=-1,-2,-3,0\nAT+SET3=-1,-2,-3\nAT+SET3=-100\n";
static const char test_case_3[] = "\nAT+SETALL=-11,-22,-33\nAT+SETALL=-1,-2,-3\nAT+SETALL=100\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\r\nERROR\r\n\r\nOK\r\n\r\nOK\r\n") == 0);
assert(strcmp(write_results, " CMD1_3:-10,-20,-30 CMD1_1:-1") == 0);
assert(var1 == -1);
assert(var2 == -20);
assert(var3 == -30);
assert(var1b == -1);
assert(var2b == -20);
assert(var3b == -30);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n") == 0);
assert(strcmp(write_results, " CMD3_3:-1,-2,-3 CMD3_1:-100") == 0);
assert(var1 == -100);
assert(var2 == -2);
assert(var3 == -3);
assert(var1b == -100);
assert(var2b == -2);
assert(var3b == -3);
prepare_input(test_case_3);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD3_3:-11,-22,-33 CMD3_3:-1,-2,-3") == 0);
assert(var1 == 100);
assert(var2 == -2);
assert(var3 == -3);
assert(var1b == 100);
assert(var2b == -2);
assert(var3b == -3);
return 0;
}

View File

@@ -1,168 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char write_results[256];
static char ack_results[256];
static uint8_t var[8];
static size_t var_write_size[4];
static int var_write_size_index;
static char const *input_text;
static size_t input_index;
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " CMD:");
strncat(write_results, data, data_size);
return 0;
}
static int var_write(const struct cat_variable *var, size_t write_size)
{
var_write_size[var_write_size_index++] = write_size;
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_BUF_STRING,
.data = var,
.data_size = sizeof(var),
.write = var_write
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
memset(var, 0, sizeof(var));
memset(var_write_size, 0, sizeof(var_write_size));
var_write_size_index = 0;
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT+SET=0\nAT+SET=\"\\\"abcd\\\"\"\nAT+SET=\"\"a\nAT+SET=\"1122334\"\nAT+SET=\"t\"\r\n";
static const char test_case_2[] = "\nAT+SET=\"12345678\"\nAT+SET=\"\"\nAT+SET=\"\\\\\\\\\"\nAT+SET=\"r1\\nr2\\n\"\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nERROR\n\nOK\n\r\nOK\r\n") == 0);
assert(strcmp(write_results, " CMD:\"\\\"abcd\\\"\" CMD:\"1122334\" CMD:\"t\"") == 0);
assert(strcmp(var, "t") == 0);
assert(var_write_size[0] == 6);
assert(var_write_size[1] == 7);
assert(var_write_size[2] == 1);
assert(var_write_size[3] == 0);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nOK\n") == 0);
assert(strcmp(write_results, " CMD:\"\" CMD:\"\\\\\\\\\" CMD:\"r1\\nr2\\n\"") == 0);
assert(strcmp(var, "r1\nr2\n") == 0);
assert(var_write_size[0] == 0);
assert(var_write_size[1] == 2);
assert(var_write_size[2] == 6);
assert(var_write_size[3] == 0);
return 0;
}

View File

@@ -1,212 +0,0 @@
/*
MIT License
Copyright (c) 2019 Marcin Borowicz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "../src/cat.h"
static char write_results[256];
static char ack_results[256];
static uint8_t var1, var1b;
static uint16_t var2, var2b;
static uint32_t var3, var3b;
static char const *input_text;
static size_t input_index;
static int cmd_write(const struct cat_command *cmd, const uint8_t *data, const size_t data_size, const size_t args_num)
{
strcat(write_results, " CMD:");
strncat(write_results, data, data_size);
return 0;
}
static int var1_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 1);
var1b = *(uint8_t*)(var->data);
return 0;
}
static int var2_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 2);
var2b = *(uint16_t*)(var->data);
return 0;
}
static int var3_write(const struct cat_variable *var, size_t write_size)
{
assert(write_size == 4);
var3b = *(uint32_t*)(var->data);
return 0;
}
static struct cat_variable vars[] = {
{
.type = CAT_VAR_UINT_DEC,
.data = &var1,
.data_size = sizeof(var1),
.write = var1_write
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var2,
.data_size = sizeof(var2),
.write = var2_write
},
{
.type = CAT_VAR_UINT_DEC,
.data = &var3,
.data_size = sizeof(var3),
.write = var3_write
}
};
static struct cat_command cmds[] = {
{
.name = "+SET",
.write = cmd_write,
.var = vars,
.var_num = sizeof(vars) / sizeof(vars[0])
}
};
static char buf[128];
static struct cat_command_group cmd_group = {
.cmd = cmds,
.cmd_num = sizeof(cmds) / sizeof(cmds[0]),
};
static struct cat_command_group *cmd_desc[] = {
&cmd_group
};
static struct cat_descriptor desc = {
.cmd_group = cmd_desc,
.cmd_group_num = sizeof(cmd_desc) / sizeof(cmd_desc[0]),
.buf = buf,
.buf_size = sizeof(buf)
};
static int write_char(char ch)
{
char str[2];
str[0] = ch;
str[1] = 0;
strcat(ack_results, str);
return 1;
}
static int read_char(char *ch)
{
if (input_index >= strlen(input_text))
return 0;
*ch = input_text[input_index];
input_index++;
return 1;
}
static struct cat_io_interface iface = {
.read = read_char,
.write = write_char
};
static void prepare_input(const char *text)
{
input_text = text;
input_index = 0;
var1 = 1;
var2 = 2;
var3 = 3;
var1b = 10;
var2b = 20;
var3b = 30;
memset(ack_results, 0, sizeof(ack_results));
memset(write_results, 0, sizeof(write_results));
}
static const char test_case_1[] = "\nAT+SET=-128\nAT+SET=0\nAT+SET=255\nAT+SET=256\n";
static const char test_case_2[] = "\nAT+SET=0,-1\nAT+SET=0,0\nAT+SET=0,65535\nAT+SET=1,65536\n";
static const char test_case_3[] = "\nAT+SET=0,0,-1\nAT+SET=0,0,0\nAT+SET=1,1,4294967295\nAT+SET=2,2,4294967296\n";
int main(int argc, char **argv)
{
struct cat_object at;
cat_init(&at, &desc, &iface, NULL);
prepare_input(test_case_1);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0 CMD:255") == 0);
assert(var1 == 255);
assert(var1b == var1);
assert(var2 == 2);
assert(var2b == 20);
assert(var3 == 3);
assert(var3b == 30);
prepare_input(test_case_2);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0,0 CMD:0,65535") == 0);
assert(var1 == 1);
assert(var1b == var1);
assert(var2 == 65535);
assert(var2b == var2);
assert(var3 == 3);
assert(var3b == 30);
prepare_input(test_case_3);
while (cat_service(&at) != 0) {};
assert(strcmp(ack_results, "\nERROR\n\nOK\n\nOK\n\nERROR\n") == 0);
assert(strcmp(write_results, " CMD:0,0,0 CMD:1,1,4294967295") == 0);
assert(var1 == 2);
assert(var1b == var1);
assert(var2 == 2);
assert(var2b == var2);
assert(var3 == 4294967295);
assert(var3b == var3);
return 0;
}

View File

@@ -1,6 +0,0 @@
#include "api.h"
void api_init(void)
{
//modbus_init();
}

View File

@@ -1,538 +0,0 @@
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_ota_ops.h"
#include "esp_timer.h"
#include "esp_chip_info.h"
#include "esp_mac.h"
#include "json.h"
#include "mqtt.h"
#include "wifi.h"
#include "timeout_utils.h"
#include "evse_error.h"
#include "evse_api.h"
#include "ocpp.h"
#include "board_config.h"
#include "socket_lock.h"
#include "proximity.h"
//#include "modbus.h"
//#include "modbus_tcp.h"
#include "rest.h"
#include "temp_sensor.h"
// #include "script.h"
#include "date_time.h"
#define RETURN_ON_ERROR(x) \
do \
{ \
esp_err_t err_rc_ = (x); \
if (unlikely(err_rc_ != ESP_OK)) \
{ \
return err_rc_; \
} \
} while (0)
cJSON *json_get_evse_config(void)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "maxChargingCurrent", evse_get_max_charging_current());
cJSON_AddNumberToObject(root, "chargingCurrent", evse_get_charging_current() / 10.0);
cJSON_AddNumberToObject(root, "defaultChargingCurrent", evse_get_default_charging_current() / 10.0);
cJSON_AddBoolToObject(root, "requireAuth", evse_is_require_auth());
cJSON_AddBoolToObject(root, "socketOutlet", evse_get_socket_outlet());
cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit());
cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit());
cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit());
cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());
cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time());
cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time());
cJSON_AddBoolToObject(root, "socketLockDetectionHigh", socket_lock_is_detection_high());
cJSON_AddNumberToObject(root, "socketLockRetryCount", socket_lock_get_retry_count());
//cJSON_AddStringToObject(root, "modelMeter", meter_model_to_str(meter_get_model()));
//cJSON_AddNumberToObject(root, "maxGridCurrent", grid_get_max_current());
//cJSON_AddStringToObject(root, "stateMeter", meter_state_to_str(meter_get_state()));
char str[64];
cJSON_AddBoolToObject(root, "enabledocpp", ocpp_get_enabled());
ocpp_get_server(str);
cJSON_AddStringToObject(root, "serverocpp", str);
ocpp_get_rfid(str);
cJSON_AddStringToObject(root, "rfid", str);
return root;
}
esp_err_t json_set_evse_config(cJSON *root)
{
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "maxChargingCurrent")))
{
RETURN_ON_ERROR(evse_set_max_charging_current(cJSON_GetObjectItem(root, "maxChargingCurrent")->valuedouble));
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "chargingCurrent")))
{
RETURN_ON_ERROR(evse_set_charging_current(cJSON_GetObjectItem(root, "chargingCurrent")->valuedouble * 10));
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultChargingCurrent")))
{
RETURN_ON_ERROR(evse_set_default_charging_current(cJSON_GetObjectItem(root, "defaultChargingCurrent")->valuedouble * 10));
}
if (cJSON_IsBool(cJSON_GetObjectItem(root, "requireAuth")))
{
evse_set_require_auth(cJSON_IsTrue(cJSON_GetObjectItem(root, "requireAuth")));
}
if (cJSON_IsBool(cJSON_GetObjectItem(root, "socketOutlet")))
{
RETURN_ON_ERROR(evse_set_socket_outlet(cJSON_IsTrue(cJSON_GetObjectItem(root, "socketOutlet"))));
}
if (cJSON_IsBool(cJSON_GetObjectItem(root, "rcm")))
{
RETURN_ON_ERROR(evse_set_rcm(cJSON_IsTrue(cJSON_GetObjectItem(root, "rcm"))));
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "temperatureThreshold")))
{
RETURN_ON_ERROR(evse_set_temp_threshold(cJSON_GetObjectItem(root, "temperatureThreshold")->valuedouble));
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "consumptionLimit")))
{
evse_set_consumption_limit(cJSON_GetObjectItem(root, "consumptionLimit")->valuedouble);
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultConsumptionLimit")))
{
evse_set_default_consumption_limit(cJSON_GetObjectItem(root, "defaultConsumptionLimit")->valuedouble);
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "chargingTimeLimit")))
{
evse_set_charging_time_limit(cJSON_GetObjectItem(root, "chargingTimeLimit")->valuedouble);
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultChargingTimeLimit")))
{
evse_set_default_charging_time_limit(cJSON_GetObjectItem(root, "defaultChargingTimeLimit")->valuedouble);
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "underPowerLimit")))
{
evse_set_under_power_limit(cJSON_GetObjectItem(root, "underPowerLimit")->valuedouble);
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultUnderPowerLimit")))
{
evse_set_default_under_power_limit(cJSON_GetObjectItem(root, "defaultUnderPowerLimit")->valuedouble);
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "socketLockOperatingTime")))
{
RETURN_ON_ERROR(socket_lock_set_operating_time(cJSON_GetObjectItem(root, "socketLockOperatingTime")->valuedouble));
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "socketLockBreakTime")))
{
RETURN_ON_ERROR(socket_lock_set_break_time(cJSON_GetObjectItem(root, "socketLockBreakTime")->valuedouble));
}
if (cJSON_IsBool(cJSON_GetObjectItem(root, "socketLockDetectionHigh")))
{
socket_lock_set_detection_high(cJSON_IsTrue(cJSON_GetObjectItem(root, "socketLockDetectionHigh")));
}
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "socketLockRetryCount")))
{
socket_lock_set_retry_count(cJSON_GetObjectItem(root, "socketLockRetryCount")->valuedouble);
}
/*
if (cJSON_IsString(cJSON_GetObjectItem(root, "modelMeter")))
{
RETURN_ON_ERROR(meter_set_model(meter_str_to_model(cJSON_GetObjectItem(root, "modelMeter")->valuestring)));
}
if (cJSON_IsString(cJSON_GetObjectItem(root, "stateMeter")))
{
RETURN_ON_ERROR(meter_set_state(meter_str_to_state(cJSON_GetObjectItem(root, "stateMeter")->valuestring)));
}
*/
if (cJSON_IsNumber(cJSON_GetObjectItem(root, "maxGridCurrent")))
{
RETURN_ON_ERROR(grid_set_max_current(cJSON_GetObjectItem(root, "maxGridCurrent")->valuedouble));
}
if (cJSON_IsBool(cJSON_GetObjectItem(root, "enabledocpp")))
{
ocpp_set_enabled(cJSON_IsTrue(cJSON_GetObjectItem(root, "enabledocpp")));
}
if (cJSON_IsString(cJSON_GetObjectItem(root, "serverocpp")))
{
ocpp_set_server(cJSON_GetStringValue(cJSON_GetObjectItem(root, "serverocpp")));
}
if (cJSON_IsString(cJSON_GetObjectItem(root, "rfid")))
{
ocpp_set_rfid(cJSON_GetStringValue(cJSON_GetObjectItem(root, "rfid")));
}
return ESP_OK;
}
cJSON *json_get_wifi_config(void)
{
cJSON *root = cJSON_CreateObject();
char str[32];
cJSON_AddBoolToObject(root, "enabled", wifi_get_enabled());
wifi_get_ssid(str);
cJSON_AddStringToObject(root, "ssid", str);
return root;
}
esp_err_t json_set_wifi_config(cJSON *root, bool timeout)
{
bool enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "enabled"));
char *ssid = cJSON_GetStringValue(cJSON_GetObjectItem(root, "ssid"));
char *password = cJSON_GetStringValue(cJSON_GetObjectItem(root, "password"));
if (timeout)
{
return timeout_wifi_set_config(enabled, ssid, password);
}
else
{
return wifi_set_config(enabled, ssid, password);
}
}
cJSON *json_get_wifi_scan(void)
{
cJSON *root = cJSON_CreateArray();
wifi_scan_ap_t scan_aps[WIFI_SCAN_SCAN_LIST_SIZE];
uint16_t number = wifi_scan(scan_aps);
for (int i = 0; i < number; i++)
{
cJSON *item = cJSON_CreateObject();
cJSON_AddStringToObject(item, "ssid", scan_aps[i].ssid);
cJSON_AddNumberToObject(item, "rssi", scan_aps[i].rssi);
cJSON_AddBoolToObject(item, "auth", scan_aps[i].auth);
cJSON_AddItemToArray(root, item);
}
return root;
}
cJSON *json_get_mqtt_config(void)
{
cJSON *root = cJSON_CreateObject();
char str[64];
cJSON_AddBoolToObject(root, "enabled", mqtt_get_enabled());
mqtt_get_server(str);
cJSON_AddStringToObject(root, "server", str);
mqtt_get_base_topic(str);
cJSON_AddStringToObject(root, "baseTopic", str);
mqtt_get_user(str);
cJSON_AddStringToObject(root, "user", str);
mqtt_get_password(str);
cJSON_AddStringToObject(root, "password", str);
cJSON_AddNumberToObject(root, "periodicity", mqtt_get_periodicity());
return root;
}
esp_err_t json_set_mqtt_config(cJSON *root)
{
bool enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "enabled"));
char *server = cJSON_GetStringValue(cJSON_GetObjectItem(root, "server"));
char *base_topic = cJSON_GetStringValue(cJSON_GetObjectItem(root, "baseTopic"));
char *user = cJSON_GetStringValue(cJSON_GetObjectItem(root, "user"));
char *password = cJSON_GetStringValue(cJSON_GetObjectItem(root, "password"));
uint16_t periodicity = cJSON_GetNumberValue(cJSON_GetObjectItem(root, "periodicity"));
return mqtt_set_config(enabled, server, base_topic, user, password, periodicity);
}
cJSON *json_get_modbus_config(void)
{
cJSON *root = cJSON_CreateObject();
//cJSON_AddBoolToObject(root, "tcpEnabled", modbus_tcp_is_enabled());
//cJSON_AddNumberToObject(root, "unitId", modbus_get_unit_id());
cJSON_AddBoolToObject(root, "tcpEnabled", false);
cJSON_AddNumberToObject(root, "unitId", 1);
return root;
}
esp_err_t json_set_modbus_config(cJSON *root)
{
//bool tcp_enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "tcpEnabled"));
//uint8_t unit_id = cJSON_GetObjectItem(root, "unitId")->valuedouble;
//modbus_tcp_set_enabled(tcp_enabled);
return 0; //modbus_set_unit_id(unit_id);
}
/*
cJSON* json_get_script_config(void)
{
cJSON* root = cJSON_CreateObject();
cJSON_AddBoolToObject(root, "enabled", script_is_enabled());
return root;
}
esp_err_t json_set_script_config(cJSON* root)
{
bool enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "enabled"));
script_set_enabled(enabled);
return ESP_OK;
}*/
cJSON *json_get_time_config(void)
{
cJSON *root = cJSON_CreateObject();
char str[64];
cJSON_AddBoolToObject(root, "ntpEnabled", date_time_is_ntp_enabled());
date_time_get_ntp_server(str);
cJSON_AddStringToObject(root, "ntpServer", str);
cJSON_AddBoolToObject(root, "ntpFromDhcp", date_time_is_ntp_from_dhcp());
date_time_get_timezone(str);
cJSON_AddStringToObject(root, "timezone", str);
return root;
}
esp_err_t json_set_time_config(cJSON *root)
{
char *ntp_server = cJSON_GetStringValue(cJSON_GetObjectItem(root, "ntpServer"));
bool ntp_enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "ntpEnabled"));
bool ntp_from_dhcp = cJSON_IsTrue(cJSON_GetObjectItem(root, "ntpFromDhcp"));
char *timezone = cJSON_GetStringValue(cJSON_GetObjectItem(root, "timezone"));
date_time_set_ntp_config(ntp_enabled, ntp_server, ntp_from_dhcp);
return date_time_set_timezone(timezone);
}
cJSON *json_get_state(void)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state()));
cJSON_AddBoolToObject(root, "available", evse_is_available());
cJSON_AddBoolToObject(root, "enabled", evse_is_enabled());
cJSON_AddBoolToObject(root, "pendingAuth", evse_is_pending_auth());
cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached());
uint32_t error = evse_error_get_bits();
if (error == 0)
{
cJSON_AddNullToObject(root, "errors");
}
else
{
cJSON *errors = cJSON_CreateArray();
if (error & EVSE_ERR_PILOT_FAULT_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("pilot_fault"));
}
if (error & EVSE_ERR_DIODE_SHORT_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("diode_short"));
}
if (error & EVSE_ERR_LOCK_FAULT_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("lock_fault"));
}
if (error & EVSE_ERR_UNLOCK_FAULT_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("unlock_fault"));
}
if (error & EVSE_ERR_RCM_TRIGGERED_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_triggered"));
}
if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_selftest_fault"));
}
if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_high"));
}
if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
{
cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_fault"));
}
cJSON_AddItemToObject(root, "errors", errors);
}
/*
cJSON_AddNumberToObject(root, "sessionTime", energy_meter_get_session_time());
cJSON_AddNumberToObject(root, "chargingTime", energy_meter_get_charging_time());
cJSON_AddNumberToObject(root, "consumption", energy_meter_get_consumption());
cJSON_AddNumberToObject(root, "power", energy_meter_get_power());
float values[3];
energy_meter_get_voltage(values);
cJSON_AddItemToObject(root, "voltage", cJSON_CreateFloatArray(values, 3));
energy_meter_get_current(values);
cJSON_AddItemToObject(root, "current", cJSON_CreateFloatArray(values, 3));
*/
return root;
}
cJSON *json_get_info(void)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "uptime", esp_timer_get_time() / 1000000);
const esp_app_desc_t *app_desc = esp_app_get_description();
cJSON_AddStringToObject(root, "appVersion", app_desc->version);
cJSON_AddStringToObject(root, "appDate", app_desc->date);
cJSON_AddStringToObject(root, "appTime", app_desc->time);
cJSON_AddStringToObject(root, "idfVersion", app_desc->idf_ver);
cJSON_AddStringToObject(root, "chip", CONFIG_IDF_TARGET);
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
cJSON_AddNumberToObject(root, "chipCores", chip_info.cores);
chip_info.revision = 301;
cJSON_AddNumberToObject(root, "chipRevision", chip_info.revision / 100);
multi_heap_info_t heap_info;
heap_caps_get_info(&heap_info, MALLOC_CAP_INTERNAL);
cJSON_AddNumberToObject(root, "heapSize", heap_info.total_allocated_bytes);
cJSON_AddNumberToObject(root, "maxHeapSize", heap_info.total_free_bytes + heap_info.total_allocated_bytes);
uint8_t mac[6];
char str[32];
esp_wifi_get_mac(ESP_IF_WIFI_STA, mac);
sprintf(str, MACSTR, MAC2STR(mac));
cJSON_AddStringToObject(root, "mac", str);
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info);
esp_ip4addr_ntoa(&ip_info.ip, str, sizeof(str));
cJSON_AddStringToObject(root, "ip", str);
esp_wifi_get_mac(ESP_IF_WIFI_AP, mac);
sprintf(str, MACSTR, MAC2STR(mac));
cJSON_AddStringToObject(root, "macAp", str);
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
esp_ip4addr_ntoa(&ip_info.ip, str, sizeof(str));
cJSON_AddStringToObject(root, "ipAp", str);
cJSON_AddNumberToObject(root, "temperatureSensorCount", temp_sensor_get_count());
cJSON_AddNumberToObject(root, "temperatureLow", temp_sensor_get_low() / 100.0);
cJSON_AddNumberToObject(root, "temperatureHigh", temp_sensor_get_high() / 100.0);
return root;
}
static const char *serial_to_str(board_config_serial_t serial)
{
switch (serial)
{
case BOARD_CONFIG_SERIAL_UART:
return "uart";
case BOARD_CONFIG_SERIAL_RS485:
return "rs485";
default:
return "none";
}
}
cJSON *json_get_board_config(void)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "deviceName", board_config.device_name);
cJSON_AddBoolToObject(root, "socketLock", board_config.socket_lock);
cJSON_AddBoolToObject(root, "proximity", board_config.proximity);
cJSON_AddNumberToObject(root, "socketLockMinBreakTime", board_config.socket_lock_min_break_time);
cJSON_AddBoolToObject(root, "rcm", board_config.rcm);
cJSON_AddBoolToObject(root, "temperatureSensor", board_config.onewire && board_config.onewire_temp_sensor);
/*
switch (board_config.energy_meter)
{
case BOARD_CONFIG_ENERGY_METER_CUR:
cJSON_AddStringToObject(root, "energyMeter", "cur");
break;
case BOARD_CONFIG_ENERGY_METER_CUR_VLT:
cJSON_AddStringToObject(root, "energyMeter", "cur_vlt");
break;
default:
cJSON_AddStringToObject(root, "energyMeter", "none");
}
//cJSON_AddBoolToObject(root, "energyMeterThreePhases", board_config.energy_meter_three_phases);
*/
cJSON_AddStringToObject(root, "serial1", serial_to_str(board_config.serial_1));
cJSON_AddStringToObject(root, "serial1Name", board_config.serial_1_name);
cJSON_AddStringToObject(root, "serial2", serial_to_str(board_config.serial_2));
cJSON_AddStringToObject(root, "serial2Name", board_config.serial_2_name);
#if SOC_UART_NUM > 2
cJSON_AddStringToObject(root, "serial3", serial_to_str(board_config.serial_3));
cJSON_AddStringToObject(root, "serial3Name", board_config.serial_3_name);
#else
cJSON_AddStringToObject(root, "serial3", serial_to_str(BOARD_CONFIG_SERIAL_NONE));
cJSON_AddStringToObject(root, "serial3Name", "");
#endif
cJSON *aux = cJSON_CreateArray();
if (board_config.aux_in_1)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_1_name));
}
if (board_config.aux_in_2)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_2_name));
}
if (board_config.aux_in_3)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_3_name));
}
if (board_config.aux_in_4)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_4_name));
}
cJSON_AddItemToObject(root, "auxIn", aux);
aux = cJSON_CreateArray();
if (board_config.aux_out_1)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_1_name));
}
if (board_config.aux_out_2)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_2_name));
}
if (board_config.aux_out_3)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_3_name));
}
if (board_config.aux_out_4)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_4_name));
}
cJSON_AddItemToObject(root, "auxOut", aux);
aux = cJSON_CreateArray();
if (board_config.aux_ain_1)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_ain_1_name));
}
if (board_config.aux_ain_2)
{
cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_ain_2_name));
}
cJSON_AddItemToObject(root, "auxAin", aux);
return root;
}

View File

@@ -1,66 +0,0 @@
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_http_client.h"
#include "cJSON.h"
#include "ota.h"
static const char* TAG = "ota";
extern const char server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const char server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
static void http_client_cleanup(esp_http_client_handle_t client)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
esp_err_t ota_get_available_version(char* version)
{
esp_http_client_config_t config = {
.url = OTA_VERSION_URL,
.cert_pem = server_cert_pem_start
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return err;
}
int content_length = esp_http_client_fetch_headers(client);
if (content_length > 0) {
esp_http_client_read(client, version, content_length);
version[content_length] = '\0';
http_client_cleanup(client);
return ESP_OK;
} else {
http_client_cleanup(client);
ESP_LOGI(TAG, "No firmware available");
return ESP_ERR_NOT_FOUND;
}
}
bool ota_is_newer_version(const char* actual, const char* available)
{
// available version has no suffix eg: vX.X.X-beta
char actual_trimed[32];
strcpy(actual_trimed, actual);
char* saveptr;
strtok_r(actual_trimed, "-", &saveptr);
bool actual_has_suffix = strtok_r(NULL, "-", &saveptr);
int diff = strcmp(available, actual_trimed);
if (diff == 0) {
return actual_has_suffix;
} else {
return diff > 0;
}
}

View File

@@ -1,74 +0,0 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "timeout_utils.h"
#include "wifi.h"
#include "rest.h"
static void restart_func(void* arg)
{
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
vTaskDelete(NULL);
}
void timeout_restart()
{
xTaskCreate(restart_func, "restart_task", 2 * 1024, NULL, 10, NULL);
}
typedef struct
{
bool enabled;
bool ssid_blank;
char ssid[32];
bool password_blank;
char password[64];
} wifi_set_config_arg_t;
static void wifi_set_config_func(void* arg)
{
vTaskDelay(pdMS_TO_TICKS(1000));
wifi_set_config_arg_t* config = (wifi_set_config_arg_t*)arg;
wifi_set_config(config->enabled, config->ssid_blank ? NULL : config->ssid, config->password_blank ? NULL : config->password);
free((void*)config);
vTaskDelete(NULL);
}
esp_err_t timeout_wifi_set_config(bool enabled, const char* ssid, const char* password)
{
if (enabled) {
if (ssid == NULL || strlen(ssid) == 0) {
char old_ssid[32];
wifi_get_ssid(old_ssid);
if (strlen(old_ssid) == 0) {
return ESP_ERR_INVALID_ARG;
}
}
}
wifi_set_config_arg_t* config = (wifi_set_config_arg_t*)malloc(sizeof(wifi_set_config_arg_t));
config->enabled = enabled;
if (ssid == NULL || ssid[0] == '\0') {
config->ssid_blank = true;
} else {
config->ssid_blank = false;
strcpy(config->ssid, ssid);
}
if (password == NULL || password[0] == '\0') {
config->password_blank = true;
} else {
config->password_blank = false;
strcpy(config->password, password);
}
xTaskCreate(wifi_set_config_func, "wifi_set_config", 4 * 1024, (void*)config, 10, NULL);
return ESP_OK;
}

View File

@@ -1,7 +1,7 @@
set(srcs "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c")
set(srcs "src/auth_types.c" "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c" "src/auth_events.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash driver esp_timer
REQUIRES esp_event esp_idf_lib_helpers evse ocpp)
PRIV_REQUIRES driver esp_timer
REQUIRES esp_event evse ocpp evse_link storage_service)

View File

@@ -1,72 +1,32 @@
#ifndef AUTH_H
#define AUTH_H
#pragma once
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include "auth_types.h" // enum + MAX LEN para API
#ifdef __cplusplus
extern "C" {
#endif
// Tamanho máximo da tag RFID (incluindo '\0')
#define AUTH_TAG_MAX_LEN 20
// Evento enviado ao EVSE Manager após leitura de tag
/* Evento auxiliar legado/útil (resultado local) */
typedef struct {
char tag[AUTH_TAG_MAX_LEN]; // Tag lida
bool authorized; // true se tag for válida
char tag[AUTH_TAG_MAX_LEN];
bool authorized;
} auth_event_t;
/**
* @brief Inicializa o sistema de autenticação.
* Carrega configuração e inicia o leitor Wiegand (wg26).
*/
void auth_init(void);
void auth_set_mode(auth_mode_t mode);
auth_mode_t auth_get_mode(void);
/**
* @brief Define a fila de eventos que receberá auth_event_t.
*/
void auth_set_event_queue(QueueHandle_t queue);
/**
* @brief Ativa ou desativa o módulo de autenticação (RFID).
* Essa configuração é salva em NVS.
*/
void auth_set_enabled(bool value);
/**
* @brief Verifica se a autenticação está habilitada.
*/
bool auth_is_enabled(void);
/**
* @brief Adiciona uma nova tag válida.
*/
bool auth_add_tag(const char *tag);
/**
* @brief Remove uma tag previamente cadastrada.
*/
bool auth_remove_tag(const char *tag);
/**
* @brief Verifica se uma tag está cadastrada.
*/
bool auth_tag_exists(const char *tag);
/**
* @brief Lista as tags registradas (via ESP_LOG).
*/
void auth_list_tags(void);
/**
* @brief Processa uma tag lida (usado pelo leitor Wiegand).
*/
void auth_process_tag(const char *tag);
void auth_wait_for_tag_registration(void);
int auth_get_tag_count(void);
const char *auth_get_tag_by_index(int index);
#ifdef __cplusplus
}
#endif
#endif // AUTH_H

View File

@@ -0,0 +1,29 @@
#pragma once
#include "esp_event.h"
#include "auth_types.h" // só tipos comuns; evita incluir auth.h
ESP_EVENT_DECLARE_BASE(AUTH_EVENTS);
/* IDs de eventos */
typedef enum {
AUTH_EVENT_TAG_PROCESSED = 0, // resultado LOCAL -> auth_tag_event_data_t
AUTH_EVENT_TAG_VERIFY, // pedir validação OCPP -> auth_tag_verify_event_t
AUTH_EVENT_TAG_SAVED, // registada (modo registo) -> auth_tag_event_data_t
AUTH_EVENT_MODE_CHANGED, // modo alterado -> auth_mode_event_data_t
AUTH_EVENT_INIT, // estado inicial -> auth_mode_event_data_t
} auth_event_id_t;
/* Payloads */
typedef struct {
char tag[AUTH_TAG_MAX_LEN];
bool authorized;
} auth_tag_event_data_t;
typedef struct {
char tag[AUTH_TAG_MAX_LEN];
uint32_t req_id; // opcional p/ correlacionar
} auth_tag_verify_event_t;
typedef struct {
auth_mode_t mode;
} auth_mode_event_data_t;

View File

@@ -0,0 +1,26 @@
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Tamanho máx. da tag (inclui NUL) */
#define AUTH_TAG_MAX_LEN 30
/* Modos de autorização */
typedef enum {
AUTH_MODE_OPEN = 0, // Sem autenticação
AUTH_MODE_LOCAL_RFID, // Lista local (NVS)
AUTH_MODE_OCPP_RFID // Validação via OCPP/CSMS
} auth_mode_t;
/* Converte enum -> "open"|"local"|"ocpp" (nunca NULL) */
const char *auth_mode_to_str(auth_mode_t mode);
/* Converte "open"|"local"|"ocpp" (case-insensitive) -> enum */
bool auth_mode_from_str(const char *s, auth_mode_t *out);
#ifdef __cplusplus
}
#endif

View File

@@ -1,151 +1,449 @@
/*
* auth.c
*/
#include "auth.h"
#include "auth_events.h"
#include "esp_event.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <esp_err.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include "wiegand_reader.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "storage_service.h"
#include "evse_link.h"
#include "evse_link_events.h"
#define MAX_TAGS 50
static const char *TAG = "Auth";
static bool enabled = true;
/* ===== Estado ===== */
static auth_mode_t s_mode = AUTH_MODE_OPEN;
static bool waiting_for_registration = false;
static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0;
static uint32_t s_next_req_id = 1;
static bool s_wiegand_started = false;
// Fila de eventos enviada ao EVSE Manager
static QueueHandle_t event_queue = NULL;
/* ===== Storage keys ===== */
#define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count"
#define NVS_MODE_KEY "mode" // uint8_t
// ===========================
// Persistência em NVS
// ===========================
// timeout para operações sync do storage
#define STORAGE_TO pdMS_TO_TICKS(2000)
static void load_auth_config(void) {
nvs_handle_t handle;
esp_err_t err = nvs_open("auth", NVS_READONLY, &handle);
if (err == ESP_OK) {
uint8_t val;
if (nvs_get_u8(handle, "enabled", &val) == ESP_OK) {
enabled = val;
ESP_LOGI(TAG, "Loaded auth enabled = %d", enabled);
}
nvs_close(handle);
} else {
ESP_LOGW(TAG, "No stored auth config found. Using default.");
}
}
static void save_auth_config(void) {
nvs_handle_t handle;
if (nvs_open("auth", NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_u8(handle, "enabled", enabled);
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "Auth config saved: enabled = %d", enabled);
} else {
ESP_LOGE(TAG, "Failed to save auth config.");
}
}
// ===========================
// Internos
// ===========================
static bool is_tag_valid(const char *tag) {
for (int i = 0; i < tag_count; i++) {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) {
/* =========================
* Helpers
* ========================= */
static bool is_tag_valid(const char *tag)
{
for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
return true;
}
}
return false;
}
// ===========================
// API pública
// ===========================
void auth_set_event_queue(QueueHandle_t queue) {
event_queue = queue;
/* =========================
* Storage Persistence (tags)
* ========================= */
static void load_tags_from_storage(void)
{
uint8_t count = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &count, STORAGE_TO);
if (err == ESP_ERR_NOT_FOUND)
{
ESP_LOGD(TAG, "No stored tags (count not found)");
tag_count = 0;
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Failed to read tag count (%s)", esp_err_to_name(err));
tag_count = 0;
return;
}
void auth_set_enabled(bool value) {
enabled = value;
save_auth_config();
ESP_LOGI(TAG, "Auth %s", enabled ? "ENABLED" : "DISABLED");
tag_count = 0;
for (int i = 0; i < (int)count && i < MAX_TAGS; i++)
{
char key[16];
char tag_buf[AUTH_TAG_MAX_LEN] = {0};
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = storage_get_str_sync(NVS_NAMESPACE, key, tag_buf, sizeof(tag_buf), STORAGE_TO);
if (err == ESP_OK)
{
if (tag_buf[0] != '\0')
{
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
}
}
else if (err == ESP_ERR_NOT_FOUND)
{
// pode acontecer se count estiver desfasado; ignora
continue;
}
else
{
ESP_LOGW(TAG, "Failed to load tag %d (%s)", i, esp_err_to_name(err));
}
}
bool auth_is_enabled(void) {
return enabled;
ESP_LOGI(TAG, "Loaded %d tags from storage", tag_count);
}
bool auth_add_tag(const char *tag) {
if (tag_count >= MAX_TAGS) return false;
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN) return false;
if (is_tag_valid(tag)) return true;
static void save_tags_to_storage(void)
{
// ler count antigo (para apagar keys antigas se removemos tags)
uint8_t old_count = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &old_count, STORAGE_TO);
if (err == ESP_ERR_NOT_FOUND)
old_count = 0;
// grava count + tags
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, (uint8_t)tag_count);
for (int i = 0; i < tag_count; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
(void)storage_set_str_async(NVS_NAMESPACE, key, valid_tags[i]);
}
// se removemos tags: apagar chaves antigas
if (old_count > (uint8_t)tag_count)
{
for (int i = tag_count; i < (int)old_count && i < MAX_TAGS; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
(void)storage_erase_key_async(NVS_NAMESPACE, key);
}
}
// opcional: forçar commit “já”
(void)storage_flush_async();
ESP_LOGD(TAG, "Tags saved to storage (%d tags)", tag_count);
}
/* =========================
* Storage Persistence (mode)
* ========================= */
static void load_mode_from_storage(void)
{
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_MODE_KEY, &u, STORAGE_TO);
if (err == ESP_OK)
{
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
s_mode = (auth_mode_t)u;
else
s_mode = AUTH_MODE_OPEN;
}
else if (err == ESP_ERR_NOT_FOUND)
{
s_mode = AUTH_MODE_OPEN;
ESP_LOGD(TAG, "No stored mode -> default OPEN");
}
else
{
s_mode = AUTH_MODE_OPEN;
ESP_LOGW(TAG, "Failed to read mode (%s) -> default OPEN", esp_err_to_name(err));
}
ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode));
}
static void save_mode_to_storage(auth_mode_t mode)
{
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_MODE_KEY, (uint8_t)mode);
(void)storage_flush_async(); // opcional: commit mais rápido
ESP_LOGD(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
}
/* =========================
* Bridge: EVSE-Link -> AUTH (remote AUTH_GRANTED no slave)
* ========================= */
static void on_remote_auth_grant(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != EVSE_LINK_EVENTS || id != LINK_EVENT_REMOTE_AUTH_GRANTED || data == NULL)
return;
const evse_link_auth_grant_event_t *src = (const evse_link_auth_grant_event_t *)data;
if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE)
return;
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, src->tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true;
ESP_LOGD(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);
esp_err_t err = esp_event_post(
AUTH_EVENTS,
AUTH_EVENT_TAG_PROCESSED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to post AUTH_EVENT_TAG_PROCESSED (remote grant): %s",
esp_err_to_name(err));
}
}
/* =========================
* Public API
* ========================= */
void auth_init(void)
{
// garantir que o storage service está pronto
ESP_ERROR_CHECK(storage_service_init());
load_mode_from_storage();
load_tags_from_storage();
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand)
{
initWiegand();
s_wiegand_started = true;
ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
}
else
{
ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
}
// bridge EVSE-Link -> AUTH
{
esp_err_t err = esp_event_handler_register(
EVSE_LINK_EVENTS,
LINK_EVENT_REMOTE_AUTH_GRANTED,
on_remote_auth_grant,
NULL);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to register EVSE-Link auth grant handler: %s",
esp_err_to_name(err));
}
}
auth_mode_event_data_t evt = {.mode = s_mode};
esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode));
}
void auth_set_mode(auth_mode_t mode)
{
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID)
{
ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
return;
}
if (mode == s_mode)
{
ESP_LOGD(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
return;
}
auth_mode_t old = s_mode;
s_mode = mode;
save_mode_to_storage(mode);
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand && !s_wiegand_started)
{
ESP_LOGD(TAG, "Mode changed %s -> %s, starting Wiegand",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
initWiegand();
s_wiegand_started = true;
}
else if (!need_wiegand && s_wiegand_started)
{
ESP_LOGD(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
}
else
{
ESP_LOGD(TAG, "Mode changed %s -> %s, no change in Wiegand state",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
}
auth_mode_event_data_t evt = {.mode = s_mode};
esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY);
}
auth_mode_t auth_get_mode(void)
{
return s_mode;
}
bool auth_add_tag(const char *tag)
{
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN)
return false;
if (tag_count >= MAX_TAGS)
return false;
if (is_tag_valid(tag))
return true;
strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
ESP_LOGI(TAG, "Tag added: %s", tag);
save_tags_to_storage();
ESP_LOGD(TAG, "Tag added: %s", tag);
return true;
}
bool auth_remove_tag(const char *tag) {
for (int i = 0; i < tag_count; i++) {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) {
for (int j = i; j < tag_count - 1; j++) {
bool auth_remove_tag(const char *tag)
{
if (!tag)
return false;
for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
{
for (int j = i; j < tag_count - 1; j++)
{
strncpy(valid_tags[j], valid_tags[j + 1], AUTH_TAG_MAX_LEN);
valid_tags[j][AUTH_TAG_MAX_LEN - 1] = '\0';
}
tag_count--;
ESP_LOGI(TAG, "Tag removed: %s", tag);
save_tags_to_storage();
ESP_LOGD(TAG, "Tag removed: %s", tag);
return true;
}
}
return false;
}
bool auth_tag_exists(const char *tag) {
bool auth_tag_exists(const char *tag)
{
if (!tag)
return false;
return is_tag_valid(tag);
}
void auth_list_tags(void) {
ESP_LOGI(TAG, "Registered Tags (%d):", tag_count);
for (int i = 0; i < tag_count; i++) {
ESP_LOGI(TAG, "- %s", valid_tags[i]);
}
void auth_list_tags(void)
{
ESP_LOGD(TAG, "Registered Tags (%d):", tag_count);
for (int i = 0; i < tag_count; i++)
ESP_LOGD(TAG, "- %s", valid_tags[i]);
}
void auth_init(void) {
load_auth_config(); // carrega estado de ativação
initWiegand(); // inicia leitor RFID
void auth_wait_for_tag_registration(void)
{
if (s_mode != AUTH_MODE_LOCAL_RFID)
{
ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
return;
}
waiting_for_registration = true;
ESP_LOGD(TAG, "Tag registration mode enabled.");
}
// Processa uma tag lida (chamada pelo leitor)
void auth_process_tag(const char *tag) {
if (!tag || !auth_is_enabled()) {
ESP_LOGW(TAG, "Auth disabled or NULL tag received.");
void auth_process_tag(const char *tag)
{
if (!tag || !*tag)
{
ESP_LOGW(TAG, "NULL/empty tag received");
return;
}
auth_event_t event;
strncpy(event.tag, tag, AUTH_TAG_MAX_LEN - 1);
event.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
event.authorized = is_tag_valid(tag);
switch (s_mode)
{
case AUTH_MODE_OPEN:
ESP_LOGD(TAG, "Mode OPEN: tag=%s (no verification)", tag);
break;
ESP_LOGI(TAG, "Tag %s: %s", tag, event.authorized ? "AUTHORIZED" : "DENIED");
case AUTH_MODE_LOCAL_RFID:
{
if (waiting_for_registration)
{
waiting_for_registration = false;
if (event_queue) {
if (xQueueSend(event_queue, &event, pdMS_TO_TICKS(100)) != pdPASS) {
ESP_LOGW(TAG, "Auth event queue full, dropping tag: %s", tag);
if (auth_add_tag(tag))
{
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
&ev, sizeof(ev), portMAX_DELAY);
ESP_LOGD(TAG, "Tag registered: %s", tag);
}
} else {
ESP_LOGW(TAG, "Auth event queue not set");
else
{
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
}
return;
}
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = is_tag_valid(tag);
ESP_LOGD(TAG, "LOCAL tag %s: %s", tag,
ev.authorized ? "AUTHORIZED" : "DENIED");
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED,
&ev, sizeof(ev), portMAX_DELAY);
break;
}
case AUTH_MODE_OCPP_RFID:
{
auth_tag_verify_event_t rq = {0};
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
rq.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
rq.req_id = s_next_req_id++;
ESP_LOGD(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
rq.tag, (unsigned)rq.req_id);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
&rq, sizeof(rq), portMAX_DELAY);
break;
}
}
}
int auth_get_tag_count(void)
{
return tag_count;
}
const char *auth_get_tag_by_index(int index)
{
if (index < 0 || index >= tag_count)
return NULL;
return valid_tags[index];
}

View File

@@ -0,0 +1,3 @@
#include "auth_events.h"
ESP_EVENT_DEFINE_BASE(AUTH_EVENTS);

View File

@@ -0,0 +1,19 @@
#include "auth_types.h"
#include <strings.h> // strcasecmp
const char *auth_mode_to_str(auth_mode_t mode) {
switch (mode) {
case AUTH_MODE_OPEN: return "open";
case AUTH_MODE_LOCAL_RFID: return "local";
case AUTH_MODE_OCPP_RFID: return "ocpp";
default: return "open";
}
}
bool auth_mode_from_str(const char *s, auth_mode_t *out) {
if (!s || !out) return false;
if (!strcasecmp(s, "open")) { *out = AUTH_MODE_OPEN; return true; }
if (!strcasecmp(s, "local")) { *out = AUTH_MODE_LOCAL_RFID; return true; }
if (!strcasecmp(s, "ocpp")) { *out = AUTH_MODE_OCPP_RFID; return true; }
return false;
}

View File

@@ -1,3 +1,4 @@
// === Início de: components/auth/src/wiegand.c ===
/**
* @file wiegand.c
*
@@ -6,9 +7,13 @@
#include <esp_log.h>
#include <string.h>
#include <stdlib.h>
#include <esp_idf_lib_helpers.h>
#include <esp_attr.h> // <- para IRAM_ATTR
#include "wiegand.h"
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif
static const char *TAG = "wiegand";
#define TIMER_INTERVAL_US 50000 // 50ms
@@ -39,11 +44,7 @@ static void isr_enable(wiegand_reader_t *reader)
gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE);
}
#if HELPER_TARGET_IS_ESP32
static void IRAM_ATTR isr_handler(void *arg)
#else
static void isr_handler(void *arg)
#endif
{
wiegand_reader_t *reader = (wiegand_reader_t *)arg;
if (!reader->enabled)
@@ -101,9 +102,11 @@ esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio
{
CHECK_ARG(reader && buf_size && callback);
/*
esp_err_t res = gpio_install_isr_service(0);
if (res != ESP_OK && res != ESP_ERR_INVALID_STATE)
return res;
*/
memset(reader, 0, sizeof(wiegand_reader_t));
reader->gpio_d0 = gpio_d0;
@@ -179,3 +182,5 @@ esp_err_t wiegand_reader_done(wiegand_reader_t *reader)
return ESP_OK;
}
// === Fim de: components/auth/src/wiegand.c ===

View File

@@ -1,90 +1,307 @@
/*
* wiegand_reader.c
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <esp_log.h>
#include <wiegand.h>
#include <evse_api.h>
#include <ocpp.h>
#include "auth.h"
#define CONFIG_EXAMPLE_BUF_SIZE 50
#define IDTAG_MAX_LEN 20
static const char *TAG = "WiegandReader";
// ---- Parâmetro global/local para controlar a convenção de paridade ----
static const bool PARITY_INVERTED = false; // mude para true se o seu leitor vier "invertido"
static wiegand_reader_t reader;
static QueueHandle_t queue = NULL;
typedef struct {
typedef struct
{
uint8_t data[CONFIG_EXAMPLE_BUF_SIZE];
size_t bits;
size_t bytes;
} data_packet_t;
static void reader_callback(wiegand_reader_t *r) {
data_packet_t p;
p.bits = r->bits;
memcpy(p.data, r->buf, CONFIG_EXAMPLE_BUF_SIZE);
xQueueSendToBack(queue, &p, 0);
static inline uint8_t get_bit_msb_first(const uint8_t *buf, size_t bit_index)
{
return (buf[bit_index / 8] >> (7 - (bit_index % 8))) & 0x01;
}
static void wiegand_task(void *arg) {
queue = xQueueCreate(5, sizeof(data_packet_t));
if (!queue) {
// Versão parametrizável de verificação de paridade
static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert)
{
if (bits != 26 && bits != 34)
return false;
const int first_parity = get_bit_msb_first(buf, 0);
const int last_parity = get_bit_msb_first(buf, bits - 1);
uint32_t left = 0, right = 0;
if (bits == 26)
{
for (int i = 1; i <= 12; i++)
if (get_bit_msb_first(buf, i))
left++;
for (int i = 13; i <= 24; i++)
if (get_bit_msb_first(buf, i))
right++;
}
else // 34 bits
{
for (int i = 1; i <= 16; i++)
if (get_bit_msb_first(buf, i))
left++;
for (int i = 17; i <= 32; i++)
if (get_bit_msb_first(buf, i))
right++;
}
// Regra padrão (W26/W34 “normais”): primeiro = PAR (left), último = ÍMPAR (right)
bool exp_first = (left % 2 == 0);
bool exp_last = (right % 2 == 1);
// Se invertido, troca as expectativas (par <-> ímpar)
if (invert)
{
exp_first = !exp_first;
exp_last = !exp_last;
}
return (first_parity == exp_first) && (last_parity == exp_last);
}
// CRC-8/ATM (polinómio 0x07, init 0x00, sem ref, xor 0x00)
static uint8_t crc8_atm(const uint8_t *data, size_t len)
{
uint8_t crc = 0x00;
for (size_t i = 0; i < len; i++)
{
crc ^= data[i];
for (int b = 0; b < 8; b++)
{
crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x07) : (uint8_t)(crc << 1);
}
}
return crc;
}
// CRC-16/IBM (CRC-16-ANSI) poly 0xA001 (refin/refout), init 0xFFFF
static uint16_t crc16_ibm(const uint8_t *data, size_t len)
{
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++)
{
crc ^= data[i];
for (int b = 0; b < 8; b++)
{
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc >>= 1;
}
}
return crc;
}
// Extrai payload (sem paridades) e devolve FC/CN para W26/34
static bool wiegand_extract_fc_cn(const uint8_t *buf, size_t bits, uint32_t *fc, uint32_t *cn)
{
if (bits != 26 && bits != 34)
return false;
uint32_t payload = 0;
size_t payload_bits = bits - 2;
for (size_t i = 0; i < payload_bits; i++)
{
size_t bit = 1 + i; // ignora bit de paridade inicial
uint8_t bitval = (buf[bit / 8] >> (7 - (bit % 8))) & 0x01;
payload = (payload << 1) | bitval;
}
if (bits == 26)
{
*fc = (payload >> 16) & 0xFF; // 8b
*cn = payload & 0xFFFF; // 16b
}
else // 34 bits
{
*fc = (payload >> 16) & 0xFFFF; // 16b
*cn = payload & 0xFFFF; // 16b
}
return true;
}
static bool build_idtag_w26_4B(uint32_t fc, uint32_t cn, char *out, size_t outlen)
{
uint8_t raw[4] = {
(uint8_t)(fc & 0xFF),
(uint8_t)((cn >> 8) & 0xFF),
(uint8_t)(cn & 0xFF),
0};
raw[3] = crc8_atm(raw, 3);
if (outlen < 9)
return false;
int n = snprintf(out, outlen, "%02X%02X%02X%02X",
raw[0], raw[1], raw[2], raw[3]);
return (n > 0 && (size_t)n < outlen);
}
static bool build_idtag_w34_7B(uint32_t fc, uint32_t cn, char *out, size_t outlen)
{
uint8_t raw[7];
raw[0] = 0x34;
raw[1] = (uint8_t)((fc >> 8) & 0xFF);
raw[2] = (uint8_t)(fc & 0xFF);
raw[3] = (uint8_t)((cn >> 8) & 0xFF);
raw[4] = (uint8_t)(cn & 0xFF);
uint16_t crc = crc16_ibm(raw, 5);
raw[5] = (uint8_t)((crc >> 8) & 0xFF);
raw[6] = (uint8_t)(crc & 0xFF);
if (outlen < 15)
return false;
int n = snprintf(out, outlen, "%02X%02X%02X%02X%02X%02X%02X",
raw[0], raw[1], raw[2], raw[3],
raw[4], raw[5], raw[6]);
return (n > 0 && (size_t)n < outlen);
}
// Callback chamado pelo esp_timer_task (contexto de task, NÃO ISR)
static void reader_callback(wiegand_reader_t *r)
{
if (!queue)
{
ESP_LOGW(TAG, "Queue not ready, dropping packet");
return;
}
data_packet_t p = {0};
p.bits = r->bits;
p.bytes = (r->bits + 7) / 8;
if (p.bytes > sizeof(p.data))
p.bytes = sizeof(p.data);
memcpy(p.data, r->buf, p.bytes);
if (xQueueSendToBack(queue, &p, 0) != pdPASS)
{
ESP_LOGW(TAG, "Queue full, dropping Wiegand packet");
}
}
static void wiegand_task(void *arg)
{
queue = xQueueCreate(8, sizeof(data_packet_t));
if (!queue)
{
ESP_LOGE(TAG, "Failed to create queue");
vTaskDelete(NULL);
return;
}
ESP_ERROR_CHECK(wiegand_reader_init(&reader, 19, 18,
true, CONFIG_EXAMPLE_BUF_SIZE, reader_callback, WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST));
ESP_ERROR_CHECK(wiegand_reader_init(&reader,
21, 22, // GPIO D0, D1
true, // internal pullups
CONFIG_EXAMPLE_BUF_SIZE,
reader_callback,
WIEGAND_MSB_FIRST,
WIEGAND_LSB_FIRST));
data_packet_t p;
while (1) {
for (;;)
{
ESP_LOGI(TAG, "Waiting for Wiegand data...");
if (xQueueReceive(queue, &p, portMAX_DELAY) == pdPASS) {
ESP_LOGI(TAG, "Bits received: %d", p.bits);
char tag[20] = {0};
if (xQueueReceive(queue, &p, portMAX_DELAY) != pdPASS)
continue;
if (p.bits == 26) {
snprintf(tag, sizeof(tag), "%03d%03d%03d", p.data[0], p.data[1], p.data[2]);
} else if (p.bits == 34) {
snprintf(tag, sizeof(tag), "%03d%03d%03d%03d", p.data[0], p.data[1], p.data[2], p.data[3]);
} else {
ESP_LOGI(TAG, "Bits received: %d", (int)p.bits);
if (!check_parity_param(p.data, p.bits, PARITY_INVERTED))
{
ESP_LOGW(TAG, "Parity check failed (%d bits). Dump:", (int)p.bits);
ESP_LOG_BUFFER_HEX(TAG, p.data, p.bytes);
continue;
}
char tag[IDTAG_MAX_LEN + 1] = {0}; // OCPP 1.6: máx 20 chars (+NUL)
uint32_t fc = 0, cn = 0;
if (!wiegand_extract_fc_cn(p.data, p.bits, &fc, &cn))
{
ESP_LOGW(TAG, "Unsupported bit length: %d", (int)p.bits);
continue;
}
ESP_LOGI(TAG, "Tag read: %s", tag);
bool ok = false;
if (p.bits == 26)
{
ok = build_idtag_w26_4B(fc, cn, tag, sizeof(tag)); // 8 hex
}
else // 34
{
ok = build_idtag_w34_7B(fc, cn, tag, sizeof(tag)); // 14 hex
}
if (!auth_is_enabled()) {
ESP_LOGW(TAG, "Auth disabled, ignoring tag.");
if (!ok)
{
ESP_LOGW(TAG, "Failed to build idTag");
continue;
}
if (auth_tag_exists(tag)) {
ESP_LOGI(TAG, "Authorized tag. Proceeding...");
evse_authorize();
ESP_LOGI(TAG, "idTag: %s", tag); // apenas [0-9A-F], alfanumérico
auth_process_tag(tag);
}
}
if (ocpp_is_TransactionActive()) {
ocpp_end_transaction(tag);
} else {
ocpp_begin_transaction(tag);
}
} else {
ESP_LOGW(TAG, "Unauthorized tag: %s", tag);
}
static void wiegand_sim_task(void *arg)
{
// lista fixa de idTags simuladas
static const char *idtaglist[] = {
"00000041349",
"W2602312345",
"W34ABCDE123",
};
const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]);
for (;;)
{
for (size_t i = 0; i < list_size; i++)
{
ESP_LOGI(TAG, "Simulação -> idTag: %s", idtaglist[i]);
auth_process_tag(idtaglist[i]);
// espera 30 segundos entre cada tag
vTaskDelay(pdMS_TO_TICKS(30000));
}
}
}
void initWiegand(void) {
void initWiegand(void)
{
ESP_LOGI(TAG, "Initializing Wiegand reader");
xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 5, NULL);
xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL);
// Para testes, podes ativar o simulador:
// ESP_LOGI(TAG, "Inicializando Wiegand simulado");
// xTaskCreate(wiegand_sim_task, "WiegandSim",
// configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
}

View File

@@ -0,0 +1,12 @@
set(srcs
"src/buzzer.c"
"src/buzzer_events.c"
)
idf_component_register(
SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
REQUIRES esp_event
PRIV_REQUIRES driver esp_timer evse network
)

View File

@@ -0,0 +1,2 @@
version: "2.6.0"
description: Authentication component

View File

@@ -0,0 +1,66 @@
// buzzer.h
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "buzzer_events.h" // para buzzer_pattern_id_t
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Inicializa hardware, fila e task do buzzer; registra event handlers.
*/
void buzzer_init(void);
/**
* @brief Desinicializa o buzzer (opcional), removendo handlers e task.
*/
void buzzer_deinit(void);
/**
* @brief Solicita tocar um padrão de buzzer de forma assíncrona.
* @param id Enum do padrão (ver buzzer_events.h).
*/
void buzzer_play(buzzer_pattern_id_t id);
/**
* @brief Interrompe imediatamente qualquer padrão em execução e esvazia a fila.
*/
void buzzer_stop(void);
/**
* @brief Ativa/Desativa globalmente o buzzer (mute).
* Quando desativado, não toca e garante nível/desligado.
*/
void buzzer_set_enabled(bool enabled);
/**
* @brief Retorna o estado atual de enable/mute do buzzer.
*/
bool buzzer_get_enabled(void);
/**
* @brief Define quiet hours (janela silenciosa) em minutos desde 00:00.
* @param enabled Habilita/desabilita quiet hours.
* @param start_min Início (0..1439).
* @param end_min Fim (0..1439). Pode cruzar meia-noite.
*/
void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min);
/**
* @brief Ajusta a frequência PWM (somente PASSIVE/LEDC; timer exclusivo recomendado).
* @return ESP_OK em sucesso; erro se modo inválido ou argumento fora do range.
*/
esp_err_t buzzer_set_frequency(uint32_t hz);
/**
* @brief Define o duty cycle em porcentagem (0..100) para PASSIVE/LEDC.
*/
void buzzer_set_duty_percent(uint8_t pct);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esp_event.h"
ESP_EVENT_DECLARE_BASE(BUZZER_EVENTS);
typedef enum {
BUZZER_EVENT_PLAY_PATTERN,
} buzzer_event_id_t;
typedef enum {
BUZZER_PATTERN_NONE = 0,
BUZZER_PATTERN_PLUGGED,
BUZZER_PATTERN_UNPLUGGED,
BUZZER_PATTERN_CHARGING,
BUZZER_PATTERN_AP_START,
BUZZER_PATTERN_CARD_READ,
BUZZER_PATTERN_CARD_ADD,
BUZZER_PATTERN_CARD_DENIED,
BUZZER_PATTERN_FAULT,
BUZZER_PATTERN_MAX
} buzzer_pattern_id_t;
typedef struct {
buzzer_pattern_id_t pattern;
} buzzer_event_data_t;

537
components/buzzer/src/buzzer.c Executable file
View File

@@ -0,0 +1,537 @@
// buzzer.c
#include "buzzer_events.h"
#include "evse_events.h"
#include "auth_events.h"
#include "network_events.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include <time.h>
#include <string.h>
// ===================== Configuração padrão =====================
#define CONFIG_BUZZER_GPIO GPIO_NUM_27
// 1 = PASSIVE (PWM), 0 = ACTIVE (on/off)
#define CONFIG_BUZZER_MODE_PASSIVE 0
#define CONFIG_BUZZER_FREQ_HZ 3500
#define CONFIG_BUZZER_DUTY_PCT 70
#define CONFIG_BUZZER_QUEUE_LEN 8
#define CONFIG_BUZZER_TASK_STACK 2048
#define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1)
// anti-spam (gap mínimo entre toques)
#define CONFIG_BUZZER_MIN_GAP_MS 70
#define CONFIG_BUZZER_ENABLE_DEFAULT 1
// quiet hours start (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_START_MIN (22 * 60)
// quiet hours end (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_END_MIN (7 * 60)
// ===================== Tipos e estado =====================
static const char *TAG = "Buzzer";
typedef enum
{
BUZZER_MODE_ACTIVE = 0,
BUZZER_MODE_PASSIVE
} buzzer_mode_t;
typedef struct
{
uint16_t on_ms;
uint16_t off_ms;
} buzzer_step_t;
typedef struct
{
const buzzer_step_t *steps;
size_t length;
} buzzer_pattern_t;
typedef struct
{
buzzer_mode_t mode;
gpio_num_t gpio;
// PASSIVE (PWM)
uint32_t freq_hz;
uint8_t duty_percent; // 0100
ledc_mode_t ledc_speed_mode; // LEDC_LOW_SPEED_MODE
ledc_timer_t ledc_timer; // LEDC_TIMER_1
ledc_channel_t ledc_channel; // LEDC_CHANNEL_1
ledc_timer_bit_t duty_resolution; // LEDC_TIMER_10_BIT
} buzzer_config_t;
// Controlo runtime
static struct
{
bool enabled;
uint16_t quiet_start_min; // [0..1440)
uint16_t quiet_end_min; // [0..1440)
bool quiet_enabled;
} s_buzzer_ctl = {
.enabled = CONFIG_BUZZER_ENABLE_DEFAULT,
.quiet_start_min = CONFIG_BUZZER_QUIET_START_MIN,
.quiet_end_min = CONFIG_BUZZER_QUIET_END_MIN,
.quiet_enabled = false,
};
// Hardware cfg
static buzzer_config_t s_buzzer_cfg = {
.mode = CONFIG_BUZZER_MODE_PASSIVE ? BUZZER_MODE_PASSIVE : BUZZER_MODE_ACTIVE,
.gpio = CONFIG_BUZZER_GPIO,
.freq_hz = CONFIG_BUZZER_FREQ_HZ,
.duty_percent = CONFIG_BUZZER_DUTY_PCT,
.ledc_speed_mode = LEDC_LOW_SPEED_MODE,
.ledc_timer = LEDC_TIMER_1,
.ledc_channel = LEDC_CHANNEL_1,
.duty_resolution = LEDC_TIMER_10_BIT};
// Fila e task
static QueueHandle_t s_buzzer_q = NULL;
static TaskHandle_t s_buzzer_task = NULL;
// Anti-spam básico
static TickType_t s_last_play_tick = 0;
static const TickType_t s_min_gap_ticks = pdMS_TO_TICKS(CONFIG_BUZZER_MIN_GAP_MS);
// ===================== Padrões (afinados) =====================
// NOTA: os enums BUZZER_PATTERN_* vêm de buzzer_events.h
// Curtos e claros
static const buzzer_step_t pattern_plugged[] = {{180, 120}}; // ~300 ms
static const buzzer_step_t pattern_card_read[] = {{80, 80}, {80, 0}}; // 160 ms audível
// Um pouco mais longos, ritmo marcante
static const buzzer_step_t pattern_unplugged[] = {{140, 140}, {140, 140}, {140, 0}}; // ~700 ms
static const buzzer_step_t pattern_card_add[] = {{100, 100}, {100, 100}, {120, 0}}; // ~520 ms
static const buzzer_step_t pattern_ap_start[] = {{250, 150}, {250, 0}}; // ~650 ms
// Charging mais curta (evento frequente)
static const buzzer_step_t pattern_charging[] = {{80, 90}, {90, 80}, {100, 0}}; // ~440 ms
// Denied mais “duro”
static const buzzer_step_t pattern_card_denied[] = {{250, 120}, {300, 0}}; // ~670 ms
// Fault crítico (duas trincas, ~1.7 s)
static const buzzer_step_t pattern_fault[] = {
{350, 120}, {350, 120}, {450, 250}, // bloco 1
{350, 120},
{350, 120},
{450, 0} // bloco 2
};
static const buzzer_pattern_t buzzer_patterns[] = {
[BUZZER_PATTERN_PLUGGED] = {pattern_plugged, sizeof(pattern_plugged) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_UNPLUGGED] = {pattern_unplugged, sizeof(pattern_unplugged) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_CHARGING] = {pattern_charging, sizeof(pattern_charging) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_AP_START] = {pattern_ap_start, sizeof(pattern_ap_start) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_CARD_READ] = {pattern_card_read, sizeof(pattern_card_read) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_CARD_ADD] = {pattern_card_add, sizeof(pattern_card_add) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_CARD_DENIED] = {pattern_card_denied, sizeof(pattern_card_denied) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_FAULT] = {pattern_fault, sizeof(pattern_fault) / sizeof(buzzer_step_t)},
};
// ===================== Helpers HW =====================
static inline uint32_t duty_from_percent(uint8_t pct, ledc_timer_bit_t res)
{
if (pct == 0)
return 0;
if (pct > 100)
pct = 100;
uint32_t max = (1U << res) - 1U;
return (uint32_t)((pct * max) / 100U);
}
static inline bool is_pattern_valid(buzzer_pattern_id_t id)
{
return (id > BUZZER_PATTERN_NONE && id < BUZZER_PATTERN_MAX);
}
static void buzzer_on(void)
{
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
// Não mudar freq a cada beep; já foi configurada na init.
esp_err_t err1 = ledc_set_duty(s_buzzer_cfg.ledc_speed_mode,
s_buzzer_cfg.ledc_channel,
duty_from_percent(s_buzzer_cfg.duty_percent, s_buzzer_cfg.duty_resolution));
if (err1 != ESP_OK)
ESP_LOGW(TAG, "ledc_set_duty err=%s", esp_err_to_name(err1));
esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
if (err2 != ESP_OK)
ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2));
}
else
{
gpio_set_level(s_buzzer_cfg.gpio, 1);
}
}
static void buzzer_off(void)
{
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
esp_err_t err1 = ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, 0);
if (err1 != ESP_OK)
ESP_LOGW(TAG, "ledc_set_duty(0) err=%s", esp_err_to_name(err1));
esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
if (err2 != ESP_OK)
ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2));
}
else
{
gpio_set_level(s_buzzer_cfg.gpio, 0);
}
}
static bool in_quiet_hours(void)
{
if (!s_buzzer_ctl.quiet_enabled)
return false;
time_t now = time(NULL);
struct tm lt;
if (localtime_r(&now, &lt) == NULL)
return false;
uint16_t minutes = (uint16_t)(lt.tm_hour * 60 + lt.tm_min);
uint16_t start = s_buzzer_ctl.quiet_start_min;
uint16_t end = s_buzzer_ctl.quiet_end_min;
if (start == end)
return false; // desativado
if (start < end)
{
return (minutes >= start && minutes < end);
}
else
{ // janela cruza meia-noite
return (minutes >= start || minutes < end);
}
}
// ===================== Execução do padrão (apenas dentro da task) =====================
static void buzzer_execute(buzzer_pattern_id_t pattern_id)
{
if (!is_pattern_valid(pattern_id))
{
ESP_LOGW(TAG, "Invalid buzzer pattern id: %d", pattern_id);
return;
}
const buzzer_pattern_t *pattern = &buzzer_patterns[pattern_id];
for (size_t i = 0; i < pattern->length; i++)
{
buzzer_on();
vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].on_ms));
buzzer_off();
if (pattern->steps[i].off_ms > 0)
{
vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].off_ms));
}
}
}
// ===================== Task & Fila =====================
static void buzzer_task(void *arg)
{
buzzer_pattern_id_t id;
for (;;)
{
if (xQueueReceive(s_buzzer_q, &id, portMAX_DELAY) == pdTRUE)
{
ESP_LOGD(TAG, "dequeue pattern %d", id); // na buzzer_task()
if (!s_buzzer_ctl.enabled)
continue;
// Quiet hours: permitir apenas alertas críticos/negativos
if (in_quiet_hours())
{
if (!(id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED))
{
continue;
}
}
// Anti-spam global
TickType_t now = xTaskGetTickCount();
if ((now - s_last_play_tick) < s_min_gap_ticks)
{
continue;
}
s_last_play_tick = now;
buzzer_execute(id);
}
}
}
// ===================== API pública =====================
void buzzer_play(buzzer_pattern_id_t id)
{
ESP_LOGD(TAG, "enqueue pattern %d", id); // dentro de buzzer_play()
if (!is_pattern_valid(id) || s_buzzer_q == NULL)
return;
(void)xQueueSend(s_buzzer_q, &id, 0);
}
void buzzer_stop(void)
{
// Interrompe imediatamente: esvazia fila e desliga
if (s_buzzer_q)
xQueueReset(s_buzzer_q);
buzzer_off();
}
void buzzer_set_enabled(bool enabled)
{
s_buzzer_ctl.enabled = enabled;
if (!enabled)
buzzer_off();
}
bool buzzer_get_enabled(void)
{
return s_buzzer_ctl.enabled;
}
void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min)
{
s_buzzer_ctl.quiet_enabled = enabled;
if (start_min < 1440)
s_buzzer_ctl.quiet_start_min = start_min;
if (end_min < 1440)
s_buzzer_ctl.quiet_end_min = end_min;
}
// Opcional: mudar tom dinamicamente (apenas se o timer for exclusivo do buzzer)
esp_err_t buzzer_set_frequency(uint32_t hz)
{
if (s_buzzer_cfg.mode != BUZZER_MODE_PASSIVE)
return ESP_ERR_INVALID_STATE;
if (hz < 50 || hz > 20000)
return ESP_ERR_INVALID_ARG;
s_buzzer_cfg.freq_hz = hz;
return ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz);
}
void buzzer_set_duty_percent(uint8_t pct)
{
if (pct > 100)
pct = 100;
s_buzzer_cfg.duty_percent = pct;
}
// ===================== Event Handlers =====================
static void buzzer_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != BUZZER_EVENTS || id != BUZZER_EVENT_PLAY_PATTERN || data == NULL)
return;
buzzer_event_data_t *evt = (buzzer_event_data_t *)data;
if (!is_pattern_valid(evt->pattern))
{
ESP_LOGW(TAG, "Invalid buzzer pattern received: %d", evt->pattern);
return;
}
buzzer_play(evt->pattern);
}
static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
return;
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
ESP_LOGD(TAG, "EVSE event: state=%d", evt->state);
buzzer_event_data_t buzzer_evt = {0};
switch (evt->state)
{
case EVSE_STATE_EVENT_IDLE:
buzzer_evt.pattern = BUZZER_PATTERN_UNPLUGGED;
break;
case EVSE_STATE_EVENT_WAITING:
buzzer_evt.pattern = BUZZER_PATTERN_PLUGGED;
break;
case EVSE_STATE_EVENT_CHARGING:
buzzer_evt.pattern = BUZZER_PATTERN_CHARGING;
break;
case EVSE_STATE_EVENT_FAULT:
// Preempta qualquer beep em andamento
buzzer_stop();
buzzer_evt.pattern = BUZZER_PATTERN_FAULT;
break;
default:
return;
}
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY);
}
static void network_event_handler(void *handler_args, esp_event_base_t base, int32_t id, void *event_data)
{
if (base != NETWORK_EVENTS)
return;
ESP_LOGD(TAG, "Network event id=%d", (int)id);
buzzer_event_data_t evt = {0};
switch ((network_event_id_t)id)
{
case NETWORK_EVENT_AP_STARTED:
case NETWORK_EVENT_STA_CONNECTED:
// Usa padrão de AP_START para indicar rede disponível
evt.pattern = BUZZER_PATTERN_AP_START;
break;
default:
// Para já, ignorar outros eventos de rede
return;
}
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY);
}
static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
if (base != AUTH_EVENTS || event_data == NULL)
return;
buzzer_event_data_t buzzer_evt = {0};
if (id == AUTH_EVENT_TAG_PROCESSED)
{
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)event_data;
ESP_LOGD(TAG, "AUTH processed: tag=%s authorized=%d", evt->tag, evt->authorized);
buzzer_evt.pattern = evt->authorized ? BUZZER_PATTERN_CARD_READ : BUZZER_PATTERN_CARD_DENIED;
}
else if (id == AUTH_EVENT_TAG_SAVED)
{
buzzer_evt.pattern = BUZZER_PATTERN_CARD_ADD;
}
else
{
return;
}
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY);
}
// ===================== Inicialização / Deinit =====================
void buzzer_init(void)
{
// Config HW
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
ledc_timer_config_t tcfg = {
.speed_mode = s_buzzer_cfg.ledc_speed_mode,
.duty_resolution = s_buzzer_cfg.duty_resolution,
.timer_num = s_buzzer_cfg.ledc_timer,
.freq_hz = s_buzzer_cfg.freq_hz,
.clk_cfg = LEDC_AUTO_CLK};
ESP_ERROR_CHECK(ledc_timer_config(&tcfg));
ledc_channel_config_t ccfg = {
.gpio_num = s_buzzer_cfg.gpio,
.speed_mode = s_buzzer_cfg.ledc_speed_mode,
.channel = s_buzzer_cfg.ledc_channel,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = s_buzzer_cfg.ledc_timer,
.duty = 0,
.hpoint = 0};
ESP_ERROR_CHECK(ledc_channel_config(&ccfg));
}
else
{
gpio_config_t io = {
.pin_bit_mask = (1ULL << s_buzzer_cfg.gpio),
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = 0,
.pull_up_en = 0,
.intr_type = GPIO_INTR_DISABLE};
ESP_ERROR_CHECK(gpio_config(&io));
gpio_set_level(s_buzzer_cfg.gpio, 0);
}
buzzer_set_quiet_hours(false, 0, 0);
buzzer_set_enabled(true);
buzzer_off();
// Fila + Task
s_buzzer_q = xQueueCreate(CONFIG_BUZZER_QUEUE_LEN, sizeof(buzzer_pattern_id_t));
configASSERT(s_buzzer_q != NULL);
BaseType_t ok = xTaskCreatePinnedToCore(
buzzer_task,
"buzzer_task",
CONFIG_BUZZER_TASK_STACK,
NULL,
CONFIG_BUZZER_TASK_PRIO,
&s_buzzer_task,
tskNO_AFFINITY);
configASSERT(ok == pdPASS);
// Handlers
ESP_ERROR_CHECK(esp_event_handler_register(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler, NULL));
ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s), freq=%lu Hz, duty=%u%%, enabled=%d",
s_buzzer_cfg.gpio,
s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE ? "passive/PWM" : "active/ON-OFF",
(unsigned long)s_buzzer_cfg.freq_hz,
(unsigned)s_buzzer_cfg.duty_percent,
(int)s_buzzer_ctl.enabled);
}
void buzzer_deinit(void)
{
(void)esp_event_handler_unregister(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler);
(void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler);
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler);
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler);
(void)esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler);
if (s_buzzer_q)
{
vQueueDelete(s_buzzer_q);
s_buzzer_q = NULL;
}
if (s_buzzer_task)
{
vTaskDelete(s_buzzer_task);
s_buzzer_task = NULL;
}
buzzer_off();
ESP_LOGI(TAG, "Buzzer deinitialized");
}

View File

@@ -0,0 +1,3 @@
#include "buzzer_events.h"
ESP_EVENT_DEFINE_BASE(BUZZER_EVENTS);

View File

@@ -2,5 +2,5 @@ set(srcs
"board_config.c")
idf_component_register(SRCS "${srcs}"
PRIV_REQUIRES nvs_flash
PRIV_REQUIRES
INCLUDE_DIRS "include")

View File

@@ -15,32 +15,6 @@ bool atob(const char *value)
return value[0] == 'y';
}
board_config_energy_meter_t atoem(const char *value)
{
if (!strcmp(value, "cur"))
{
return BOARD_CONFIG_ENERGY_METER_CUR;
}
if (!strcmp(value, "cur_vlt"))
{
return BOARD_CONFIG_ENERGY_METER_CUR_VLT;
}
return BOARD_CONFIG_ENERGY_METER_NONE;
}
board_config_serial_t atoser(const char *value)
{
if (!strcmp(value, "uart"))
{
return BOARD_CONFIG_SERIAL_UART;
}
if (!strcmp(value, "rs485"))
{
return BOARD_CONFIG_SERIAL_RS485;
}
return BOARD_CONFIG_SERIAL_NONE;
}
#define SET_CONFIG_VALUE(name, prop, convert_fn) \
if (!strcmp(key, name)) \
{ \
@@ -94,16 +68,14 @@ void board_config_load()
if (value != NULL)
{
SET_CONFIG_VALUE_STR("DEVICE_NAME", device_name);
SET_CONFIG_VALUE("LED_CHARGING", led_charging, atob);
SET_CONFIG_VALUE("LED_CHARGING_GPIO", led_charging_gpio, atoi);
SET_CONFIG_VALUE("LED_ERROR", led_error, atob);
SET_CONFIG_VALUE("LED_ERROR_GPIO", led_error_gpio, atoi);
SET_CONFIG_VALUE("LED_STOP", led_stop, atob);
SET_CONFIG_VALUE("LED_STOP_GPIO", led_stop_gpio, atoi);
SET_CONFIG_VALUE("led_blue", led_blue, atob);
SET_CONFIG_VALUE("led_blue_GPIO", led_blue_gpio, atoi);
SET_CONFIG_VALUE("led_red", led_red, atob);
SET_CONFIG_VALUE("led_red_GPIO", led_red_gpio, atoi);
SET_CONFIG_VALUE("led_green", led_green, atob);
SET_CONFIG_VALUE("led_green_GPIO", led_green_gpio, atoi);
SET_CONFIG_VALUE("BUZZER", buzzer, atob);
SET_CONFIG_VALUE("BUZZER_GPIO", buzzer_gpio, atoi);
SET_CONFIG_VALUE("BUTTON_WIFI_GPIO", button_wifi_gpio, atoi);
SET_CONFIG_VALUE("PILOT_PWM_GPIO", pilot_pwm_gpio, atoi);
SET_CONFIG_VALUE("PILOT_ADC_CHANNEL", pilot_adc_channel, atoi);
@@ -130,76 +102,6 @@ void board_config_load()
SET_CONFIG_VALUE("RCM", rcm, atob);
SET_CONFIG_VALUE("RCM_GPIO", rcm_gpio, atoi);
SET_CONFIG_VALUE("RCM_TEST_GPIO", rcm_test_gpio, atoi);
SET_CONFIG_VALUE("ENERGY_METER", energy_meter, atoem);
SET_CONFIG_VALUE("ENERGY_METER_THREE_PHASES", energy_meter_three_phases, atob);
SET_CONFIG_VALUE("ENERGY_METER_L1_CUR_ADC_CHANNEL", energy_meter_l1_cur_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L2_CUR_ADC_CHANNEL", energy_meter_l2_cur_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L3_CUR_ADC_CHANNEL", energy_meter_l3_cur_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_CUR_SCALE", energy_meter_cur_scale, atoff);
SET_CONFIG_VALUE("ENERGY_METER_L1_VLT_ADC_CHANNEL", energy_meter_l1_vlt_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L2_VLT_ADC_CHANNEL", energy_meter_l2_vlt_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_L3_VLT_ADC_CHANNEL", energy_meter_l3_vlt_adc_channel, atoi);
SET_CONFIG_VALUE("ENERGY_METER_VLT_SCALE", energy_meter_vlt_scale, atoff);
SET_CONFIG_VALUE("AUX_IN_1", aux_in_1, atob);
SET_CONFIG_VALUE_STR("AUX_IN_1_NAME", aux_in_1_name);
SET_CONFIG_VALUE("AUX_IN_1_GPIO", aux_in_1_gpio, atoi);
SET_CONFIG_VALUE("AUX_IN_2", aux_in_2, atob);
SET_CONFIG_VALUE_STR("AUX_IN_2_NAME", aux_in_2_name);
SET_CONFIG_VALUE("AUX_IN_2_GPIO", aux_in_2_gpio, atoi);
SET_CONFIG_VALUE("AUX_IN_3", aux_in_3, atob);
SET_CONFIG_VALUE_STR("AUX_IN_3_NAME", aux_in_3_name);
SET_CONFIG_VALUE("AUX_IN_3_GPIO", aux_in_3_gpio, atoi);
SET_CONFIG_VALUE("AUX_IN_4", aux_in_4, atob);
SET_CONFIG_VALUE_STR("AUX_IN_4_NAME", aux_in_4_name);
SET_CONFIG_VALUE("AUX_IN_4_GPIO", aux_in_4_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_1", aux_out_1, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_1_NAME", aux_out_1_name);
SET_CONFIG_VALUE("AUX_OUT_1_GPIO", aux_out_1_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_2", aux_out_2, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_2_NAME", aux_out_2_name);
SET_CONFIG_VALUE("AUX_OUT_2_GPIO", aux_out_2_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_3", aux_out_3, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_3_NAME", aux_out_3_name);
SET_CONFIG_VALUE("AUX_OUT_3_GPIO", aux_out_3_gpio, atoi);
SET_CONFIG_VALUE("AUX_OUT_4", aux_out_4, atob);
SET_CONFIG_VALUE_STR("AUX_OUT_4_NAME", aux_out_4_name);
SET_CONFIG_VALUE("AUX_OUT_4_GPIO", aux_out_4_gpio, atoi);
SET_CONFIG_VALUE("AUX_AIN_1", aux_ain_1, atob);
SET_CONFIG_VALUE_STR("AUX_AIN_1_NAME", aux_ain_1_name);
SET_CONFIG_VALUE("AUX_AIN_1_ADC_CHANNEL", aux_ain_1_adc_channel, atoi);
SET_CONFIG_VALUE("AUX_AIN_2", aux_ain_2, atob);
SET_CONFIG_VALUE_STR("AUX_AIN_2_NAME", aux_ain_2_name);
SET_CONFIG_VALUE("AUX_AIN_2_ADC_CHANNEL", aux_ain_2_adc_channel, atoi);
/*
#if CONFIG_ESP_CONSOLE_UART_NUM != 0
SET_CONFIG_VALUE("SERIAL_1", serial_1, atoser);
SET_CONFIG_VALUE_STR("SERIAL_1_NAME", serial_1_name);
SET_CONFIG_VALUE("SERIAL_1_RXD_GPIO", serial_1_rxd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_1_TXD_GPIO", serial_1_txd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_1_RTS_GPIO", serial_1_rts_gpio, atoi);
#endif // CONFIG_ESP_CONSOLE_UART_NUM != 0
#if CONFIG_ESP_CONSOLE_UART_NUM != 1
SET_CONFIG_VALUE("SERIAL_2", serial_2, atoser);
SET_CONFIG_VALUE_STR("SERIAL_2_NAME", serial_2_name);
SET_CONFIG_VALUE("SERIAL_2_RXD_GPIO", serial_2_rxd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_2_TXD_GPIO", serial_2_txd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_2_RTS_GPIO", serial_2_rts_gpio, atoi);
#endif // CONFIG_ESP_CONSOLE_UART_NUM != 1
#if SOC_UART_NUM > 2
#if CONFIG_ESP_CONSOLE_UART_NUM != 2
SET_CONFIG_VALUE("SERIAL_3", serial_3, atoser);
SET_CONFIG_VALUE_STR("SERIAL_3_NAME", serial_3_name);
SET_CONFIG_VALUE("SERIAL_3_RXD_GPIO", serial_3_rxd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_3_TXD_GPIO", serial_3_txd_gpio, atoi);
SET_CONFIG_VALUE("SERIAL_3_RTS_GPIO", serial_3_rts_gpio, atoi);
#endif / CONFIG_ESP_CONSOLE_UART_NUM != 2
#endif // SOC_UART_NUM > 2
SET_CONFIG_VALUE("ONEWIRE", onewire, atob);
SET_CONFIG_VALUE("ONEWIRE_GPIO", onewire_gpio, atoi);
SET_CONFIG_VALUE("ONEWIRE_TEMP_SENSOR", onewire_temp_sensor, atob);
ESP_LOGE(TAG, "Unknown config value %s=%s", key, value);
*/
}
}
}

View File

@@ -5,13 +5,15 @@
#include "hal/gpio_types.h"
#include "soc/soc_caps.h"
typedef enum {
typedef enum
{
BOARD_CONFIG_ENERGY_METER_NONE,
BOARD_CONFIG_ENERGY_METER_CUR,
BOARD_CONFIG_ENERGY_METER_CUR_VLT
} board_config_energy_meter_t;
typedef enum {
typedef enum
{
BOARD_CONFIG_SERIAL_NONE,
BOARD_CONFIG_SERIAL_UART,
BOARD_CONFIG_SERIAL_RS485
@@ -21,12 +23,12 @@ typedef struct
{
char device_name[32];
bool led_charging : 1;
gpio_num_t led_charging_gpio;
bool led_error : 1;
gpio_num_t led_error_gpio;
bool led_stop : 1;
gpio_num_t led_stop_gpio;
bool led_blue : 1;
gpio_num_t led_blue_gpio;
bool led_red : 1;
gpio_num_t led_red_gpio;
bool led_green : 1;
gpio_num_t led_green_gpio;
bool buzzer : 1;
gpio_num_t buzzer_gpio;
@@ -63,80 +65,6 @@ typedef struct
gpio_num_t rcm_gpio;
gpio_num_t rcm_test_gpio;
board_config_energy_meter_t energy_meter;
bool energy_meter_three_phases : 1;
adc_channel_t energy_meter_l1_cur_adc_channel;
adc_channel_t energy_meter_l2_cur_adc_channel;
adc_channel_t energy_meter_l3_cur_adc_channel;
float energy_meter_cur_scale;
adc_channel_t energy_meter_l1_vlt_adc_channel;
adc_channel_t energy_meter_l2_vlt_adc_channel;
adc_channel_t energy_meter_l3_vlt_adc_channel;
float energy_meter_vlt_scale;
bool aux_in_1 : 1;
char aux_in_1_name[8];
gpio_num_t aux_in_1_gpio;
bool aux_in_2 : 1;
char aux_in_2_name[8];
gpio_num_t aux_in_2_gpio;
bool aux_in_3 : 1;
char aux_in_3_name[8];
gpio_num_t aux_in_3_gpio;
bool aux_in_4 : 1;
char aux_in_4_name[8];
gpio_num_t aux_in_4_gpio;
bool aux_out_1 : 1;
char aux_out_1_name[8];
gpio_num_t aux_out_1_gpio;
bool aux_out_2 : 1;
char aux_out_2_name[8];
gpio_num_t aux_out_2_gpio;
bool aux_out_3 : 1;
char aux_out_3_name[8];
gpio_num_t aux_out_3_gpio;
bool aux_out_4 : 1;
char aux_out_4_name[8];
gpio_num_t aux_out_4_gpio;
bool aux_ain_1 : 1;
char aux_ain_1_name[8];
adc_channel_t aux_ain_1_adc_channel;
bool aux_ain_2 : 1;
char aux_ain_2_name[8];
adc_channel_t aux_ain_2_adc_channel;
board_config_serial_t serial_1;
char serial_1_name[16];
gpio_num_t serial_1_rxd_gpio;
gpio_num_t serial_1_txd_gpio;
gpio_num_t serial_1_rts_gpio;
board_config_serial_t serial_2;
char serial_2_name[16];
gpio_num_t serial_2_rxd_gpio;
gpio_num_t serial_2_txd_gpio;
gpio_num_t serial_2_rts_gpio;
#if SOC_UART_NUM > 2
board_config_serial_t serial_3;
char serial_3_name[16];
gpio_num_t serial_3_rxd_gpio;
gpio_num_t serial_3_txd_gpio;
gpio_num_t serial_3_rts_gpio;
#endif /* SOC_UART_NUM > 2 */
bool onewire : 1;
gpio_num_t onewire_gpio;
bool onewire_temp_sensor : 1;
} board_config_t;
extern board_config_t board_config;

View File

@@ -1,20 +0,0 @@
name: esp_idf_lib_helpers
description: Common support library for esp-idf-lib
version: 1.2.0
groups:
- common
code_owners:
- trombik
- UncleRus
depends:
- freertos
thread_safe: n/a
targets:
- esp32
- esp8266
- esp32s2
- esp32c3
license: ISC
copyrights:
- name: trombik
year: 2019

View File

@@ -1,4 +0,0 @@
idf_component_register(
INCLUDE_DIRS .
REQUIRES freertos
)

View File

@@ -1,13 +0,0 @@
Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,8 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS = .
ifdef CONFIG_IDF_TARGET_ESP8266
COMPONENT_DEPENDS = esp8266 freertos
else
COMPONENT_DEPENDS = freertos
endif

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if !defined(__ESP_IDF_LIB_HELPERS__H__)
#define __ESP_IDF_LIB_HELPERS__H__
/* XXX this header file does not need to include freertos/FreeRTOS.h.
* but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct
* order. as this header depends on sdkconfig.h, sdkconfig.h must be included
* first. however, the SDK includes this header first, then includes
* `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an
* evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed
* build.
*/
#include <freertos/FreeRTOS.h>
#include <esp_idf_version.h>
#if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL)
#error Unknown ESP-IDF/ESP8266 RTOS SDK version
#endif
/* Minimal supported version for ESP32, ESP32S2 */
#define HELPER_ESP32_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 5)
/* Minimal supported version for ESP8266 */
#define HELPER_ESP8266_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 0)
/* HELPER_TARGET_IS_ESP32
* 1 when the target is esp32
*/
#if defined(CONFIG_IDF_TARGET_ESP32) \
|| defined(CONFIG_IDF_TARGET_ESP32S2) \
|| defined(CONFIG_IDF_TARGET_ESP32S3) \
|| defined(CONFIG_IDF_TARGET_ESP32C2) \
|| defined(CONFIG_IDF_TARGET_ESP32C3) \
|| defined(CONFIG_IDF_TARGET_ESP32C6) \
|| defined(CONFIG_IDF_TARGET_ESP32H2)
#define HELPER_TARGET_IS_ESP32 (1)
#define HELPER_TARGET_IS_ESP8266 (0)
/* HELPER_TARGET_IS_ESP8266
* 1 when the target is esp8266
*/
#elif defined(CONFIG_IDF_TARGET_ESP8266)
#define HELPER_TARGET_IS_ESP32 (0)
#define HELPER_TARGET_IS_ESP8266 (1)
#else
#error BUG: cannot determine the target
#endif
#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER
#error Unsupported ESP-IDF version. Please update!
#endif
#if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER
#error Unsupported ESP8266 RTOS SDK version. Please update!
#endif
/* show the actual values for debugging */
#if DEBUG
#define VALUE_TO_STRING(x) #x
#define VALUE(x) VALUE_TO_STRING(x)
#define VAR_NAME_VALUE(var) #var "=" VALUE(var)
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32H2))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32))
#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266))
#pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR))
#endif
#endif

View File

@@ -1,21 +0,0 @@
#if CONFIG_IDF_TARGET_ESP32
#include <esp32/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C2
#include <esp32c2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C3
#include <esp32c3/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32C6
#include <esp32c6/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32H2
#include <esp32h2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32H4
#include <esp32h4/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32S2
#include <esp32s2/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP32S3
#include <esp32s3/rom/ets_sys.h>
#elif CONFIG_IDF_TARGET_ESP8266
#include <rom/ets_sys.h>
#else
#error "ets_sys: Unknown target"
#endif

View File

@@ -8,11 +8,15 @@ set(srcs
evse_fsm.c
evse_manager.c
evse_hardware.c
evse_pilot.c
evse_meter.c
evse_session.c
evse_api.c
)
idf_component_register(
SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash
REQUIRES peripherals auth
PRIV_REQUIRES driver
REQUIRES peripherals auth loadbalancer scheduler storage_service
)

37
components/evse/evse_api.c Executable file
View File

@@ -0,0 +1,37 @@
// evse_api.c - Main EVSE control logic
#include "evse_fsm.h"
#include "evse_error.h"
#include "evse_limits.h"
#include "evse_config.h"
#include "evse_api.h"
#include "evse_session.h"
#include "evse_pilot.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
static const char *TAG = "evse_api";
// ================================
// Public Configuration Interface
// ================================
void evse_set_enabled(bool value) {
ESP_LOGI(TAG, "Set enabled %d", value);
evse_config_set_enabled(value);
}
bool evse_is_available(void) {
return evse_config_is_available();
}
void evse_set_available(bool value) {
ESP_LOGI(TAG, "Set available %d", value);
evse_config_set_available(value);
}
bool evse_get_session(evse_session_t *out) {
return evse_session_get(out);
}

View File

@@ -1,279 +1,410 @@
#include <inttypes.h> // Include for PRI macros
#include <inttypes.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "evse_config.h"
#include "board_config.h"
#include "evse_limits.h"
#include "evse_api.h"
#include "evse_state.h"
#include "esp_log.h"
#include "nvs.h"
#include "esp_err.h"
#include "esp_check.h"
#include "storage_service.h"
static const char *TAG = "evse_config";
#define NVS_NAMESPACE "evse_config"
static nvs_handle_t nvs;
// ========================
// 3 variáveis (semântica simples)
// ========================
// Configurable parameters
static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
static uint8_t grid_max_current = MAX_GRID_CURRENT_LIMIT;
static uint16_t charging_current;
static bool socket_outlet;
static bool rcm;
// 1) Hardware (FIXO)
static const uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
// 2) Configurável (persistido)
static uint16_t charging_current = MAX_CHARGING_CURRENT_LIMIT;
// 3) Runtime (RAM)
static uint16_t charging_current_runtime = 0;
// Outros parâmetros (persistidos)
static bool socket_outlet = false;
static bool rcm = false;
static uint8_t temp_threshold = 60;
static bool require_auth;
esp_err_t evse_config_init(void) {
ESP_LOGD(TAG, "Initializing NVS configuration...");
ESP_LOGI(TAG, "Opening NVS namespace");
return nvs_open("evse", NVS_READWRITE, &nvs);
// Availability / Enable flags (persistidos)
static bool is_available = true;
static bool is_enabled = true;
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
// Ajusta conforme o teu boot:
// 1000ms pode ser curto com Wi-Fi/FS/tasks; 2000ms é mais robusto em produto.
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(2000); }
// ========================
// Initialization
// ========================
esp_err_t evse_config_init(void)
{
// garante storage iniciado
ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
ESP_LOGI(TAG, "EVSE config init OK (storage-backed)");
return ESP_OK;
}
void evse_check_defaults(void) {
void evse_check_defaults(void)
{
esp_err_t err;
uint8_t u8;
uint16_t u16;
uint32_t u32;
bool needs_commit = false;
uint8_t u8 = 0;
uint16_t u16 = 0;
ESP_LOGD(TAG, "Checking default parameters...");
// Timeouts: leitura e escrita no boot
const TickType_t rd_to = BOOT_TO();
const TickType_t wr_to = TO_TICKS_MS(2000);
// Max charging current
err = nvs_get_u8(nvs, "max_chrg_curr", &u8);
ESP_LOGD(TAG, "Max charging current read: %d", u8);
if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT) {
max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
nvs_set_u8(nvs, "max_chrg_curr", max_charging_current);
needs_commit = true;
ESP_LOGD(TAG, "Max charging current adjusted to: %d", max_charging_current);
} else {
max_charging_current = u8;
ESP_LOGD(TAG, "Checking default parameters (sync persistence)...");
// -----------------------------------------
// Charging current (default, persisted)
// -----------------------------------------
err = storage_get_u16_sync(NVS_NAMESPACE, "def_chrg_curr", &u16, rd_to);
if (err != ESP_OK || u16 < MIN_CHARGING_CURRENT_LIMIT || u16 > max_charging_current)
{
charging_current = max_charging_current;
esp_err_t se = storage_set_u16_sync(NVS_NAMESPACE, "def_chrg_curr", charging_current, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist def_chrg_curr=%u: %s",
(unsigned)charging_current, esp_err_to_name(se));
// seguimos com RAM correta; persist pode falhar por flash/partição
}
// Grid max current
err = nvs_get_u8(nvs, "grid_max_curr", &u8);
ESP_LOGD(TAG, "Grid max current read: %d", u8);
if (err != ESP_OK || u8 < MIN_GRID_CURRENT_LIMIT || u8 > MAX_GRID_CURRENT_LIMIT) {
grid_max_current = MAX_GRID_CURRENT_LIMIT;
nvs_set_u8(nvs, "grid_max_curr", grid_max_current);
needs_commit = true;
ESP_LOGD(TAG, "Grid max current adjusted to: %d", grid_max_current);
} else {
grid_max_current = u8;
ESP_LOGW(TAG, "Invalid/missing def_chrg_curr (%s) -> reset to %u (sync persisted)",
esp_err_to_name(err), (unsigned)charging_current);
}
// Charging current (decA)
err = nvs_get_u16(nvs, "def_chrg_curr", &u16);
ESP_LOGD(TAG, "Charging current read: %d", u16);
if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT * 10) || u16 > (max_charging_current * 10)) {
charging_current = max_charging_current * 10;
nvs_set_u16(nvs, "def_chrg_curr", charging_current);
needs_commit = true;
ESP_LOGD(TAG, "Charging current adjusted to: %d", charging_current);
} else {
else
{
charging_current = u16;
}
err = nvs_get_u8(nvs, "require_auth", &u8);
require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false;
if (err != ESP_OK) {
nvs_set_u8(nvs, "require_auth", require_auth);
needs_commit = true;
ESP_LOGD(TAG, "Require auth adjusted to: %d", require_auth);
// runtime inicializa a partir do default
charging_current_runtime = charging_current;
ESP_LOGD(TAG, "Runtime charging current initialized from default: %u",
(unsigned)charging_current_runtime);
// -----------------------------------------
// Socket outlet (persisted) + capability gate
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "socket_outlet", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{
bool wanted = (u8 != 0);
if (wanted && !board_config.proximity)
{
// NVS dizia 1, mas HW não suporta -> runtime false e persistir 0
socket_outlet = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist socket_outlet=0 (capability mismatch): %s",
esp_err_to_name(se));
}
err = nvs_get_u8(nvs, "socket_outlet", &u8);
socket_outlet = (err == ESP_OK && u8) && board_config.proximity;
if (err != ESP_OK) {
nvs_set_u8(nvs, "socket_outlet", socket_outlet);
needs_commit = true;
ESP_LOGD(TAG, "Socket outlet adjusted to: %d", socket_outlet);
ESP_LOGW(TAG, "socket_outlet requested but HW has no proximity -> forcing false (sync persisted)");
}
else
{
socket_outlet = wanted;
}
}
else
{
socket_outlet = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist socket_outlet default=0: %s", esp_err_to_name(se));
}
err = nvs_get_u8(nvs, "rcm", &u8);
rcm = (err == ESP_OK && u8) && board_config.rcm;
if (err != ESP_OK) {
nvs_set_u8(nvs, "rcm", rcm);
needs_commit = true;
ESP_LOGD(TAG, "RCM adjusted to: %d", rcm);
ESP_LOGW(TAG, "Missing/invalid socket_outlet (%s) -> default=false (sync persisted).",
esp_err_to_name(err));
}
err = nvs_get_u8(nvs, "temp_threshold", &u8);
temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60;
if (err != ESP_OK) {
nvs_set_u8(nvs, "temp_threshold", temp_threshold);
needs_commit = true;
ESP_LOGD(TAG, "Temp threshold adjusted to: %d", temp_threshold);
// -----------------------------------------
// RCM (persisted) + capability gate
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "rcm", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{
bool wanted = (u8 != 0);
if (wanted && !board_config.rcm)
{
rcm = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist rcm=0 (capability mismatch): %s",
esp_err_to_name(se));
}
// Additional limits
if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK) {
evse_set_consumption_limit(u32);
ESP_LOGD(TAG, "Consumption limit read and applied: %" PRIu32, u32); // Updated to PRIu32
ESP_LOGW(TAG, "rcm requested but HW has no RCM -> forcing false (sync persisted)");
}
else
{
rcm = wanted;
}
}
else
{
rcm = false;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist rcm default=0: %s", esp_err_to_name(se));
}
if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK) {
evse_set_charging_time_limit(u32);
ESP_LOGD(TAG, "Charging time limit read and applied: %" PRIu32, u32); // Updated to PRIu32
ESP_LOGW(TAG, "Missing/invalid rcm (%s) -> default=false (sync persisted).",
esp_err_to_name(err));
}
if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK) {
evse_set_under_power_limit(u16);
ESP_LOGD(TAG, "Under power limit read and applied: %d", u16);
// -----------------------------------------
// Temp threshold (persisted)
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "temp_threshold", &u8, rd_to);
if (err == ESP_OK && u8 >= 40 && u8 <= 80)
{
temp_threshold = u8;
}
else
{
temp_threshold = 60;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "temp_threshold", temp_threshold, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist temp_threshold=%u: %s",
(unsigned)temp_threshold, esp_err_to_name(se));
}
if (needs_commit) {
nvs_commit(nvs);
ESP_LOGD(TAG, "Changes committed to NVS.");
}
ESP_LOGW(TAG, "Invalid/missing temp_threshold (%s) -> default=60 (sync persisted).",
esp_err_to_name(err));
}
// Current
uint8_t evse_get_max_charging_current(void) {
ESP_LOGI(TAG, "Max charging current read: %d", max_charging_current);
return max_charging_current;
// -----------------------------------------
// Availability (persisted) [0/1]
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "available", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{
is_available = (u8 != 0);
}
else
{
is_available = true;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "available", 1, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist available=1: %s", esp_err_to_name(se));
}
esp_err_t evse_set_max_charging_current(uint8_t value) {
ESP_LOGI(TAG, "Attempting to set max charging current: %d", value);
if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT)
ESP_LOGW(TAG, "Missing/invalid 'available' (%s) -> default=true (sync persisted).",
esp_err_to_name(err));
}
// -----------------------------------------
// Enabled (persisted) [0/1]
// -----------------------------------------
err = storage_get_u8_sync(NVS_NAMESPACE, "enabled", &u8, rd_to);
if (err == ESP_OK && u8 <= 1)
{
is_enabled = (u8 != 0);
}
else
{
is_enabled = true;
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "enabled", 1, wr_to);
if (se != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist enabled=1: %s", esp_err_to_name(se));
}
ESP_LOGW(TAG, "Missing/invalid 'enabled' (%s) -> default=true (sync persisted).",
esp_err_to_name(err));
}
// Flush explícito no boot:
// - ajuda a garantir commit determinístico antes do resto do sistema avançar
// - mantém-se útil mesmo com setters sync se o teu storage ainda estiver com debounce interno
esp_err_t fe = storage_flush_sync(wr_to);
if (fe != ESP_OK)
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
}
// ========================
// Charging current getters/setters
// ========================
uint8_t evse_get_max_charging_current(void) { return max_charging_current; }
uint16_t evse_get_charging_current(void) { return charging_current; }
esp_err_t evse_set_charging_current(uint16_t value)
{
if (value < MIN_CHARGING_CURRENT_LIMIT || value > max_charging_current)
return ESP_ERR_INVALID_ARG;
max_charging_current = value;
nvs_set_u8(nvs, "max_chrg_curr", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "Max charging current adjusted to: %d", max_charging_current);
if (value == charging_current)
{
evse_set_runtime_charging_current(value);
return ESP_OK;
}
uint8_t grid_get_max_current(void) {
ESP_LOGD(TAG, "Grid max current read: %d", grid_max_current);
return grid_max_current;
}
esp_err_t grid_set_max_current(uint8_t value) {
ESP_LOGD(TAG, "Attempting to set grid max current: %d", value);
if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT)
return ESP_ERR_INVALID_ARG;
grid_max_current = value;
nvs_set_u8(nvs, "grid_max_curr", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "Grid max current adjusted to: %d", grid_max_current);
return ESP_OK;
}
uint16_t evse_get_charging_current(void) {
ESP_LOGD(TAG, "Charging current read: %d", charging_current);
return charging_current;
}
esp_err_t evse_set_charging_current(uint16_t value) {
ESP_LOGD(TAG, "Attempting to set charging current: %d", value);
if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10))
return ESP_ERR_INVALID_ARG;
charging_current = value;
nvs_set_u16(nvs, "def_chrg_curr", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "Charging current adjusted to: %d", charging_current);
esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_chrg_curr", value);
if (err != ESP_OK)
{
// Em runtime, isto pode falhar por fila cheia. RAM fica correta; persistência é best-effort.
ESP_LOGE(TAG, "Failed to persist def_chrg_curr async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
evse_set_runtime_charging_current(value);
return ESP_OK;
}
uint16_t evse_get_default_charging_current(void) {
uint16_t value;
nvs_get_u16(nvs, "def_chrg_curr", &value);
ESP_LOGD(TAG, "Default charging current read: %d", value);
return value;
// ========================
// Runtime current (not saved)
// ========================
void evse_set_runtime_charging_current(uint16_t value)
{
if (value > max_charging_current)
value = max_charging_current;
else if (value < MIN_CHARGING_CURRENT_LIMIT)
value = MIN_CHARGING_CURRENT_LIMIT;
charging_current_runtime = value;
}
esp_err_t evse_set_default_charging_current(uint16_t value) {
ESP_LOGD(TAG, "Attempting to set default charging current: %d", value);
if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10))
return ESP_ERR_INVALID_ARG;
nvs_set_u16(nvs, "def_chrg_curr", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "Default charging current adjusted to: %d", value);
return ESP_OK;
}
uint16_t evse_get_runtime_charging_current(void) { return charging_current_runtime; }
// ========================
// Socket outlet
bool evse_get_socket_outlet(void) {
ESP_LOGD(TAG, "Socket outlet read: %d", socket_outlet);
return socket_outlet;
}
// ========================
bool evse_get_socket_outlet(void) { return socket_outlet; }
esp_err_t evse_set_socket_outlet(bool value)
{
if (value && !board_config.proximity)
return ESP_ERR_INVALID_ARG;
if (value == socket_outlet)
return ESP_OK;
esp_err_t evse_set_socket_outlet(bool value) {
ESP_LOGD(TAG, "Attempting to set socket outlet: %d", value);
if (value && !board_config.proximity) return ESP_ERR_INVALID_ARG;
socket_outlet = value;
nvs_set_u8(nvs, "socket_outlet", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "Socket outlet adjusted to: %d", socket_outlet);
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "socket_outlet", (uint8_t)value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist socket_outlet async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
return ESP_OK;
}
// ========================
// RCM
bool evse_is_rcm(void) {
ESP_LOGD(TAG, "RCM read: %d", rcm);
return rcm;
}
// ========================
bool evse_is_rcm(void) { return rcm; }
esp_err_t evse_set_rcm(bool value)
{
if (value && !board_config.rcm)
return ESP_ERR_INVALID_ARG;
if (value == rcm)
return ESP_OK;
esp_err_t evse_set_rcm(bool value) {
ESP_LOGD(TAG, "Attempting to set RCM: %d", value);
if (value && !board_config.rcm) return ESP_ERR_INVALID_ARG;
rcm = value;
nvs_set_u8(nvs, "rcm", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "RCM adjusted to: %d", rcm);
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "rcm", (uint8_t)value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist rcm async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
return ESP_OK;
}
// ========================
// Temperature
uint8_t evse_get_temp_threshold(void) {
ESP_LOGD(TAG, "Temp threshold read: %d", temp_threshold);
return temp_threshold;
// ========================
uint8_t evse_get_temp_threshold(void) { return temp_threshold; }
esp_err_t evse_set_temp_threshold(uint8_t value)
{
if (value < 40 || value > 80)
return ESP_ERR_INVALID_ARG;
if (value == temp_threshold)
return ESP_OK;
temp_threshold = value;
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "temp_threshold", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist temp_threshold async=%u: %s", (unsigned)value, esp_err_to_name(err));
return err;
}
esp_err_t evse_set_temp_threshold(uint8_t value) {
ESP_LOGI(TAG, "Attempting to set temp threshold: %d", value);
if (value < 40 || value > 80) return ESP_ERR_INVALID_ARG;
temp_threshold = value;
nvs_set_u8(nvs, "temp_threshold", value);
nvs_commit(nvs);
ESP_LOGI(TAG, "Temp threshold adjusted to: %d", temp_threshold);
return ESP_OK;
}
// Authentication
bool evse_is_require_auth(void) {
ESP_LOGD(TAG, "Require auth read: %d", require_auth);
return require_auth;
}
void evse_set_require_auth(bool value) {
ESP_LOGI(TAG, "Attempting to set require auth: %d", value);
require_auth = value;
nvs_set_u8(nvs, "require_auth", value);
nvs_commit(nvs);
ESP_LOGD(TAG, "Require auth adjusted to: %d", require_auth);
}
// ========================
// Availability
static bool is_available = true;
// ========================
bool evse_config_is_available(void) { return is_available; }
bool evse_config_is_available(void) {
ESP_LOGD(TAG, "Checking availability: %d", is_available);
return is_available;
}
void evse_config_set_available(bool available) {
ESP_LOGD(TAG, "Setting availability to: %d", available);
is_available = available;
void evse_config_set_available(bool available)
{
bool newv = available;
if (newv == is_available)
return;
is_available = newv;
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "available", (uint8_t)is_available);
if (err != ESP_OK)
ESP_LOGE(TAG, "Failed to persist 'available' async=%u: %s", (unsigned)is_available, esp_err_to_name(err));
}
// ========================
// Enable/Disable
static bool is_enabled = true;
// ========================
bool evse_config_is_enabled(void) { return is_enabled; }
bool evse_config_is_enabled(void) {
ESP_LOGD(TAG, "Checking if enabled: %d", is_enabled);
return is_enabled;
}
void evse_config_set_enabled(bool enabled)
{
bool newv = enabled;
if (newv == is_enabled)
return;
void evse_config_set_enabled(bool enabled) {
ESP_LOGD(TAG, "Setting enabled state to: %d", enabled);
is_enabled = enabled;
is_enabled = newv;
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "enabled", (uint8_t)is_enabled);
if (err != ESP_OK)
ESP_LOGE(TAG, "Failed to persist 'enabled' async=%u: %s", (unsigned)is_enabled, esp_err_to_name(err));
}

View File

@@ -1,11 +1,11 @@
// evse_core.c - Função principal de controle do EVSE
// components/evse/evse_core.c
#include "evse_fsm.h"
#include "evse_error.h"
#include "evse_limits.h"
#include "evse_config.h"
#include "evse_api.h"
#include "pilot.h"
#include "evse_session.h"
#include "evse_pilot.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
@@ -13,107 +13,116 @@
static const char *TAG = "evse_core";
static SemaphoreHandle_t mutex;
static evse_state_t last_state = EVSE_STATE_A;
static bool authorized = false;
static TickType_t auth_grant_to = 0;
// Filtro simples de histerese no pilot
#define PILOT_STABLE_SAMPLES 2
static pilot_voltage_t s_last_raw = PILOT_VOLTAGE_12;
static pilot_voltage_t s_filtered = PILOT_VOLTAGE_12;
static int s_stable_count = 0;
static pilot_voltage_t filter_pilot_voltage(pilot_voltage_t raw)
{
if (raw == s_last_raw)
{
if (s_stable_count < PILOT_STABLE_SAMPLES)
{
s_stable_count++;
}
}
else
{
s_last_raw = raw;
s_stable_count = 1;
}
if (s_stable_count >= PILOT_STABLE_SAMPLES && raw != s_filtered)
{
s_filtered = raw;
}
return s_filtered;
}
static void evse_process(void);
static void evse_core_task(void *arg);
void evse_init(void) {
void evse_init(void)
{
ESP_LOGI(TAG, "EVSE Init");
mutex = xSemaphoreCreateMutex();
if (!mutex)
{
ESP_LOGE(TAG, "Failed to create EVSE core mutex");
return;
}
evse_check_defaults();
evse_fsm_reset();
pilot_set_level(true); // Estado inicial do piloto
pilot_set_level(true);
xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL);
BaseType_t rc = xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 6, NULL);
configASSERT(rc == pdPASS);
}
static void evse_process(void)
{
if (!mutex)
{
return;
}
void evse_process(void) {
xSemaphoreTake(mutex, portMAX_DELAY);
pilot_voltage_t pilot_voltage;
pilot_voltage_t pilot_raw;
bool is_n12v = false;
pilot_measure(&pilot_voltage, &is_n12v);
ESP_LOGD(TAG, "Pilot: %d, -12V: %s", pilot_voltage, is_n12v ? "yes" : "no");
pilot_measure(&pilot_raw, &is_n12v);
pilot_voltage_t pilot_voltage = filter_pilot_voltage(pilot_raw);
ESP_LOGD(TAG, "Pilot(raw=%d, filt=%d), -12V: %s",
pilot_raw, pilot_voltage, is_n12v ? "yes" : "no");
// raw set/clear; erro visível mantém holdoff interno (60s após sumir)
evse_error_check(pilot_voltage, is_n12v);
if (evse_get_error() == 0 && !evse_is_error_cleared()) {
bool authorized = evse_state_get_authorized();
// ✅ Sem cooldown externo: disponibilidade depende só do erro "visível"
bool available = evse_config_is_available() && (evse_get_error() == 0);
bool enabled = evse_config_is_enabled();
evse_fsm_process(
pilot_voltage,
authorized,
evse_config_is_available(),
evse_config_is_enabled()
);
evse_state_get_authorized(),
available,
enabled);
evse_limits_check(evse_get_state());
evse_limits_check();
if (evse_is_limit_reached())
{
if (evse_state_get_authorized())
{
ESP_LOGW(TAG, "Charging limit reached → revoking authorization");
evse_state_set_authorized(false);
}
}
evse_state_t current = evse_get_state();
if (current != last_state) {
ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current));
if (current != last_state)
{
last_state = current;
}
}
if (evse_get_error() == 0) {
evse_mark_error_cleared();
}
xSemaphoreGive(mutex);
}
// ================================
// Interface pública
// ================================
bool evse_is_enabled(void) {
return evse_config_is_enabled();
}
void evse_set_enabled(bool value) {
ESP_LOGI(TAG, "Set enabled %d", value);
evse_config_set_enabled(value);
}
bool evse_is_available(void) {
return evse_config_is_available();
}
void evse_set_available(bool value) {
ESP_LOGI(TAG, "Set available %d", value);
evse_config_set_available(value);
}
bool evse_is_pending_auth(void) {
return evse_state_is_session(evse_get_state()) && !authorized;
}
void evse_authorize(void) {
ESP_LOGI(TAG, "Authorize");
evse_state_set_authorized(true);
}
void evse_set_authorized(bool value) {
ESP_LOGI(TAG, "Set authorized %d", value);
xSemaphoreTake(mutex, portMAX_DELAY);
authorized = value;
xSemaphoreGive(mutex);
}
// ================================
// Tarefa principal
// ================================
static void evse_core_task(void *arg) {
while (true) {
static void evse_core_task(void *arg)
{
(void)arg;
while (true)
{
evse_process();
vTaskDelay(pdMS_TO_TICKS(100));
}

View File

@@ -1,119 +1,310 @@
#include "evse_error.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"
#include "esp_log.h"
#include "ntc_sensor.h"
#include "esp_event.h"
#include "esp_timer.h"
#include "evse_events.h"
#include "evse_config.h"
static const char *TAG = "evse_error";
static uint32_t error_bits = 0;
static TickType_t auto_clear_timeout = 0;
// ----------------------------------------------------
// Estado interno
// ----------------------------------------------------
// raw_bits = erros “instantâneos” conforme checks (set/clear)
// visible_bits = erros expostos ao resto do sistema (com holdoff)
// clear_deadline = quando pode finalmente limpar visible_bits para 0
static uint32_t raw_bits = 0;
static uint32_t visible_bits = 0;
static TickType_t clear_deadline = 0;
// Sticky flag: "todos erros visíveis foram limpos"
static bool error_cleared = false;
void evse_error_init(void) {
// Inicialização do sistema de erros
}
// Proteção contra concorrência
static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v) {
ESP_LOGD(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s",
pilot_voltage, is_n12v ? "true" : "false");
// ----------------------------------------------------
// Helper: publicar evento de alteração de erro (visible_bits)
// ----------------------------------------------------
static void evse_error_post_event(uint32_t new_bits, uint32_t changed_mask)
{
evse_error_event_data_t ev = {
.error_bits = new_bits,
.changed_mask = changed_mask,
.timestamp_us = esp_timer_get_time(),
};
// Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1) {
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
esp_err_t err = esp_event_post(
EVSE_EVENTS,
EVSE_EVENT_ERROR_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s",
esp_err_to_name(err));
}
}
// Falta de -12V durante PWM (C ou D)
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) {
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
}
}
// ----------------------------------------------------
// Helpers internos
// ----------------------------------------------------
static bool raw_has_bit(uint32_t bit)
{
bool v;
portENTER_CRITICAL(&error_mux);
v = ((raw_bits & bit) != 0);
portEXIT_CRITICAL(&error_mux);
return v;
}
void evse_temperature_check(void) {
float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida)
uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável
// Log informativo com os valores
ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold);
// Se a temperatura parecer inválida, aplica erro de sensor
if (temp_c < -40.0f || temp_c > 150.0f) {
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
}
static void reconcile_visible_locked(TickType_t now)
{
// Se existem erros reais, o visível segue imediatamente
if (raw_bits != 0)
{
visible_bits = raw_bits;
clear_deadline = 0;
error_cleared = false;
return;
}
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida
if (temp_c >= threshold) {
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold);
}
} else {
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
// raw_bits == 0
if (visible_bits == 0)
{
clear_deadline = 0;
return;
}
uint32_t evse_get_error(void) {
return error_bits;
// Ainda há erro visível (holdoff). Arma deadline 1x.
if (clear_deadline == 0)
{
clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS);
return;
}
bool evse_is_error_cleared(void) {
return error_cleared;
}
void evse_mark_error_cleared(void) {
error_cleared = false;
}
// Já existentes
void evse_error_set(uint32_t bitmask) {
error_bits |= bitmask;
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) {
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s
}
}
void evse_error_clear(uint32_t bitmask) {
bool had_error = error_bits != 0;
error_bits &= ~bitmask;
if (had_error && error_bits == 0) {
// Expirou -> limpar finalmente
if ((int32_t)(now - clear_deadline) >= 0)
{
visible_bits = 0;
clear_deadline = 0;
error_cleared = true;
}
}
void evse_error_tick(void) {
if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && xTaskGetTickCount() >= auto_clear_timeout) {
evse_error_clear(EVSE_ERR_AUTO_CLEAR_BITS);
auto_clear_timeout = 0;
}
}
// ----------------------------------------------------
// API pública
// ----------------------------------------------------
void evse_error_init(void)
{
uint32_t old_vis, new_vis, changed;
bool post = false;
bool evse_error_is_active(void) {
return error_bits != 0;
}
portENTER_CRITICAL(&error_mux);
uint32_t evse_error_get_bits(void) {
return error_bits;
}
old_vis = visible_bits;
void evse_error_reset_flag(void) {
raw_bits = 0;
visible_bits = 0;
clear_deadline = 0;
error_cleared = false;
new_vis = visible_bits;
changed = old_vis ^ new_vis;
post = (changed != 0);
portEXIT_CRITICAL(&error_mux);
if (post)
{
evse_error_post_event(new_vis, changed);
}
}
bool evse_error_cleared_flag(void) {
return error_cleared;
uint32_t evse_get_error(void)
{
portENTER_CRITICAL(&error_mux);
uint32_t val = visible_bits;
portEXIT_CRITICAL(&error_mux);
return val;
}
bool evse_error_is_active(void)
{
return evse_get_error() != 0;
}
uint32_t evse_error_get_bits(void)
{
return evse_get_error();
}
bool evse_error_cleared_flag(void)
{
portENTER_CRITICAL(&error_mux);
bool v = error_cleared;
portEXIT_CRITICAL(&error_mux);
return v;
}
void evse_error_reset_flag(void)
{
portENTER_CRITICAL(&error_mux);
error_cleared = false;
portEXIT_CRITICAL(&error_mux);
}
void evse_error_set(uint32_t bitmask)
{
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
raw_bits |= bitmask;
// se aparece qualquer erro, o "cleared" deixa de ser verdade
error_cleared = false;
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
}
void evse_error_clear(uint32_t bitmask)
{
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
raw_bits &= ~bitmask;
// ✅ Aqui é onde o “60s depois do erro desaparecer” é armado:
// quando raw_bits chega a 0, reconcile arma clear_deadline (uma vez)
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
}
void evse_error_tick(void)
{
uint32_t old_vis, new_vis, changed;
TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&error_mux);
old_vis = visible_bits;
reconcile_visible_locked(now);
new_vis = visible_bits;
changed = old_vis ^ new_vis;
portEXIT_CRITICAL(&error_mux);
if (changed != 0)
{
evse_error_post_event(new_vis, changed);
}
}
// ----------------------------------------------------
// Checks (raw -> set/clear)
// ----------------------------------------------------
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
{
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
pilot_voltage, is_n12v ? "true" : "false");
// 1) Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1)
{
if (!raw_has_bit(EVSE_ERR_PILOT_FAULT_BIT))
{
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
}
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
}
else
{
evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT);
}
// 2) Falta de -12V durante PWM (C ou D)
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v)
{
if (!raw_has_bit(EVSE_ERR_DIODE_SHORT_BIT))
{
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
}
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
}
else
{
evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
}
}
void evse_temperature_check(void)
{
float temp_c = ntc_temp_sensor();
uint8_t threshold = evse_get_temp_threshold();
ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
temp_c, threshold);
// Temperatura inválida -> erro de sensor
if (temp_c < -40.0f || temp_c > 150.0f)
{
if (!raw_has_bit(EVSE_ERR_TEMPERATURE_FAULT_BIT))
{
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
}
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
return;
}
// Leitura válida -> limpa erro de sensor
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);
// Temperatura máxima
if (temp_c >= threshold)
{
if (!raw_has_bit(EVSE_ERR_TEMPERATURE_HIGH_BIT))
{
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
temp_c, threshold);
}
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
else
{
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
}

View File

@@ -1,3 +1,3 @@
#include "evse_events.h"
ESP_EVENT_DEFINE_BASE(EVSE_EVENT);
ESP_EVENT_DEFINE_BASE(EVSE_EVENTS);

View File

@@ -1,8 +1,8 @@
// evse_fsm.c - Máquina de Estados EVSE com controle centralizado
// components/evse/evse_fsm.c
#include "evse_fsm.h"
#include "evse_api.h"
#include "pilot.h"
#include "evse_pilot.h"
#include "evse_config.h"
#include "esp_log.h"
#include "ac_relay.h"
#include "board_config.h"
@@ -10,6 +10,7 @@
#include "proximity.h"
#include "rcm.h"
#include "evse_state.h"
#include "evse_error.h"
static const char *TAG = "evse_fsm";
@@ -17,99 +18,178 @@ static const char *TAG = "evse_fsm";
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
static bool c1_d1_waiting = false;
static TickType_t c1_d1_relay_to = 0;
void evse_fsm_reset(void) {
void evse_fsm_reset(void)
{
evse_set_state(EVSE_STATE_A);
c1_d1_waiting = false;
c1_d1_relay_to = 0;
}
static void update_outputs(evse_state_t state, uint16_t charging_current, uint8_t cable_max_current, bool socket_outlet) {
switch (state) {
/**
* @brief Atualiza saídas de hardware (pilot, relé, trava) em função do estado lógico.
*/
static void update_outputs(evse_state_t state)
{
const uint16_t current = evse_get_runtime_charging_current();
uint8_t cable_max_current = evse_get_max_charging_current();
const bool socket_outlet = evse_get_socket_outlet();
if (socket_outlet)
{
cable_max_current = proximity_get_max_current();
}
// Segurança total: qualquer erro ativo força saída segura
if (evse_get_error() != 0)
{
if (ac_relay_get_state())
{
ac_relay_set_state(false);
ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!");
}
else
{
ac_relay_set_state(false);
}
// Em erro, garantir pilot OFF (não PWM / não +12V)
pilot_set_level(true);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false);
}
return;
}
switch (state)
{
case EVSE_STATE_A:
case EVSE_STATE_E:
case EVSE_STATE_F:
ac_relay_set_state(false);
// A → pilot alto (+12V), E/F → pilot OFF
pilot_set_level(state == EVSE_STATE_A);
if (board_config.socket_lock && socket_outlet) {
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false);
}
//energy_meter_stop_session();
break;
case EVSE_STATE_B1:
pilot_set_level(true);
ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet) {
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
if (rcm_test()) {
ESP_LOGI(TAG, "RCM self test passed");
} else {
ESP_LOGW(TAG, "RCM self test failed");
}
if (socket_outlet) {
cable_max_current = proximity_get_max_current();
}
//energy_meter_start_session();
(void)rcm_test();
break;
case EVSE_STATE_B2:
pilot_set_amps(MIN(charging_current, cable_max_current * 10));
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
break;
case EVSE_STATE_C1:
case EVSE_STATE_D1:
pilot_set_level(true);
c1_d1_waiting = true;
c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000);
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
break;
case EVSE_STATE_C2:
case EVSE_STATE_D2:
pilot_set_amps(MIN(charging_current, cable_max_current * 10));
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(true);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
break;
}
}
void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled) {
TickType_t now = xTaskGetTickCount();
evse_state_t previous_state = evse_get_state();
evse_state_t current_state = previous_state;
/**
* @brief Máquina de estados principal do EVSE (IEC 61851).
*/
void evse_fsm_process(
pilot_voltage_t pilot_voltage,
bool authorized,
bool available,
bool enabled)
{
// 1) Erros globais: dominam qualquer outra lógica
uint32_t err_bits = evse_get_error();
if (err_bits != 0)
{
evse_state_t forced_state =
(err_bits & EVSE_ERR_PILOT_FAULT_BIT) ? EVSE_STATE_E : EVSE_STATE_F;
switch (current_state) {
if (evse_get_state() != forced_state)
{
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado %s",
evse_state_to_str(forced_state));
evse_set_state(forced_state);
}
update_outputs(forced_state);
return;
}
evse_state_t curr = evse_get_state();
switch (curr)
{
case EVSE_STATE_A:
if (!available) {
if (!available)
{
evse_set_state(EVSE_STATE_F);
} else if (pilot_voltage == PILOT_VOLTAGE_9) {
}
else if (pilot_voltage == PILOT_VOLTAGE_9)
{
evse_set_state(EVSE_STATE_B1);
}
break;
case EVSE_STATE_B1:
case EVSE_STATE_B2:
if (!available) {
if (!available)
{
evse_set_state(EVSE_STATE_F);
break;
}
switch (pilot_voltage) {
switch (pilot_voltage)
{
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break;
case PILOT_VOLTAGE_3:
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
break;
default:
break;
}
@@ -117,58 +197,66 @@ void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool avail
case EVSE_STATE_C1:
case EVSE_STATE_D1:
if (c1_d1_waiting && now >= c1_d1_relay_to) {
ac_relay_set_state(false);
c1_d1_waiting = false;
if (!available) {
case EVSE_STATE_C2:
case EVSE_STATE_D2:
if (!available)
{
evse_set_state(EVSE_STATE_F);
break;
}
if (!enabled)
{
if (curr == EVSE_STATE_C2)
{
evse_set_state(EVSE_STATE_C1);
}
else if (curr == EVSE_STATE_D2)
{
evse_set_state(EVSE_STATE_D1);
}
// fallthrough intencional
case EVSE_STATE_C2:
case EVSE_STATE_D2:
if (!enabled || !available) {
evse_set_state(
(current_state == EVSE_STATE_D2 || current_state == EVSE_STATE_D1)
? EVSE_STATE_D1
: EVSE_STATE_C1
);
break;
}
switch (pilot_voltage) {
switch (pilot_voltage)
{
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break;
case PILOT_VOLTAGE_3:
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
default:
break;
}
break;
case EVSE_STATE_E:
// Sem transições a partir de E
// ✅ Agora recupera como F: se disponível e sem erro -> volta a A
if (available && evse_get_error() == 0)
{
evse_set_state(EVSE_STATE_A);
}
break;
case EVSE_STATE_F:
if (available) {
if (available && evse_get_error() == 0)
{
evse_set_state(EVSE_STATE_A);
}
break;
}
evse_state_t new_state = evse_get_state();
if (new_state != previous_state) {
ESP_LOGI(TAG, "State changed: %s -> %s", evse_state_to_str(previous_state), evse_state_to_str(new_state));
update_outputs(new_state, evse_get_charging_current(), evse_get_max_charging_current(), evse_get_socket_outlet());
}
evse_state_t next = evse_get_state();
update_outputs(next);
}

View File

@@ -1,5 +1,5 @@
#include "evse_hardware.h"
#include "pilot.h"
#include "evse_pilot.h"
#include "ac_relay.h"
#include "socket_lock.h"
#include "proximity.h"
@@ -7,6 +7,7 @@
static const char *TAG = "evse_hardware";
void evse_hardware_init(void) {
pilot_init();
pilot_set_level(true); // Sinal piloto em 12V (inicial)
ac_relay_set_state(false); // Relé desligado
//socket_lock_set_locked(false); // Destrava o conector

View File

@@ -1,97 +1,301 @@
#include "evse_limits.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
// ========================
// Estado interno
// ========================
#include "evse_state.h"
#include "evse_api.h"
#include "evse_limits.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_check.h"
#include "storage_service.h"
#define NVS_NAMESPACE "evse_limits"
static const char *TAG = "evse_limits";
static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
static bool limit_reached = false;
static uint32_t consumption_limit = 0;
static uint32_t charging_time_limit = 0;
static uint16_t under_power_limit = 0;
static uint32_t consumption_limit = 0; // Wh
static uint32_t charging_time_limit = 0; // seconds
static uint16_t under_power_limit = 0; // W
static uint32_t default_consumption_limit = 0;
static uint32_t default_charging_time_limit = 0;
static uint16_t default_under_power_limit = 0;
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(1000); }
// ========================
// Estado de controle
// ========================
void evse_set_limit_reached(uint8_t value) {
limit_reached = (value != 0);
// ---------------------------------
// Init + defaults
// ---------------------------------
esp_err_t evse_limits_init(void)
{
ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
ESP_LOGI(TAG, "EVSE limits init OK (storage-backed)");
return ESP_OK;
}
bool evse_is_limit_reached(void) {
return limit_reached;
void evse_limits_check_defaults(void)
{
esp_err_t err;
bool needs_flush = false;
uint32_t u32 = 0;
uint16_t u16 = 0;
ESP_LOGD(TAG, "Checking default limits...");
// Consumption limit (Wh) default = 0 (disabled)
err = storage_get_u32_sync(NVS_NAMESPACE, "def_cons_lim", &u32, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
consumption_limit = u32;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
consumption_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_cons_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
// ========================
// Limites em tempo de execução
// ========================
// Charging time limit (s) default = 0 (disabled)
err = storage_get_u32_sync(NVS_NAMESPACE, "def_ch_time_lim", &u32, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
charging_time_limit = u32;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
charging_time_limit = 0;
portEXIT_CRITICAL(&evse_mux);
uint32_t evse_get_consumption_limit(void) {
return consumption_limit;
(void)storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_ch_time_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
void evse_set_consumption_limit(uint32_t value) {
// Under-power limit (W) default = 0 (disabled)
err = storage_get_u16_sync(NVS_NAMESPACE, "def_un_pwr_lim", &u16, BOOT_TO());
if (err == ESP_OK)
{
portENTER_CRITICAL(&evse_mux);
under_power_limit = u16;
portEXIT_CRITICAL(&evse_mux);
}
else
{
portENTER_CRITICAL(&evse_mux);
under_power_limit = 0;
portEXIT_CRITICAL(&evse_mux);
(void)storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing def_un_pwr_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
}
if (needs_flush)
{
esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000));
if (fe != ESP_OK)
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
else
ESP_LOGD(TAG, "Defaults committed (flush).");
}
}
// ---------------------------------
// Limit reached flag
// ---------------------------------
bool evse_get_limit_reached(void)
{
bool val;
portENTER_CRITICAL(&evse_mux);
val = limit_reached;
portEXIT_CRITICAL(&evse_mux);
return val;
}
void evse_set_limit_reached(bool v)
{
portENTER_CRITICAL(&evse_mux);
limit_reached = v;
portEXIT_CRITICAL(&evse_mux);
}
bool evse_is_limit_reached(void)
{
return evse_get_limit_reached();
}
// ---------------------------------
// Consumption limit
// ---------------------------------
uint32_t evse_get_consumption_limit(void)
{
uint32_t val;
portENTER_CRITICAL(&evse_mux);
val = consumption_limit;
portEXIT_CRITICAL(&evse_mux);
return val;
}
void evse_set_consumption_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
if (consumption_limit != value)
{
consumption_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist consumption limit (%" PRIu32 " Wh): %s",
value, esp_err_to_name(err));
}
}
uint32_t evse_get_charging_time_limit(void) {
return charging_time_limit;
// ---------------------------------
// Charging time limit
// ---------------------------------
uint32_t evse_get_charging_time_limit(void)
{
uint32_t val;
portENTER_CRITICAL(&evse_mux);
val = charging_time_limit;
portEXIT_CRITICAL(&evse_mux);
return val;
}
void evse_set_charging_time_limit(uint32_t value) {
void evse_set_charging_time_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
if (charging_time_limit != value)
{
charging_time_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
if (!changed)
return;
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist charging time limit (%" PRIu32 " s): %s",
value, esp_err_to_name(err));
}
}
uint16_t evse_get_under_power_limit(void) {
return under_power_limit;
// ---------------------------------
// Under-power limit
// ---------------------------------
uint16_t evse_get_under_power_limit(void)
{
uint16_t val;
portENTER_CRITICAL(&evse_mux);
val = under_power_limit;
portEXIT_CRITICAL(&evse_mux);
return val;
}
void evse_set_under_power_limit(uint16_t value) {
void evse_set_under_power_limit(uint16_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
if (under_power_limit != value)
{
under_power_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
// ========================
// Limites padrão (persistentes)
// ========================
if (!changed)
return;
uint32_t evse_get_default_consumption_limit(void) {
return default_consumption_limit;
}
void evse_set_default_consumption_limit(uint32_t value) {
default_consumption_limit = value;
}
uint32_t evse_get_default_charging_time_limit(void) {
return default_charging_time_limit;
}
void evse_set_default_charging_time_limit(uint32_t value) {
default_charging_time_limit = value;
}
uint16_t evse_get_default_under_power_limit(void) {
return default_under_power_limit;
}
void evse_set_default_under_power_limit(uint16_t value) {
default_under_power_limit = value;
}
// ========================
// Lógica de verificação de limites
// ========================
void evse_limits_check(evse_state_t state) {
// Se algum limite estiver ativo, verifique o estado
if ((consumption_limit > 0 || charging_time_limit > 0 || under_power_limit > 0)
&& evse_state_is_charging(state)) {
// (Lógica real a ser aplicada aqui, ex: medição de consumo, tempo ou potência)
evse_set_limit_reached(1);
esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value);
if (err != ESP_OK)
{
ESP_LOGE(TAG,
"Failed to persist under-power limit (%" PRIu32 " W): %s",
(uint32_t)value, esp_err_to_name(err));
}
}
// ---------------------------------
// Runtime check
// ---------------------------------
void evse_limits_check(void)
{
// Só faz sentido quando há energia ativa (C2/D2)
if (!evse_state_is_charging(evse_get_state()))
return;
evse_session_t sess;
if (!evse_session_get(&sess) || !sess.is_current)
return;
uint32_t cons_lim;
uint32_t time_lim;
uint16_t unp_lim;
portENTER_CRITICAL(&evse_mux);
cons_lim = consumption_limit;
time_lim = charging_time_limit;
unp_lim = under_power_limit;
portEXIT_CRITICAL(&evse_mux);
bool reached = false;
if (cons_lim > 0 && sess.energy_wh >= cons_lim)
{
ESP_LOGW(TAG, "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
sess.energy_wh, cons_lim);
reached = true;
}
if (time_lim > 0 && sess.duration_s >= time_lim)
{
ESP_LOGW(TAG, "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
sess.duration_s, time_lim);
reached = true;
}
int32_t p = evse_meter_get_instant_power();
uint32_t inst_power = (p > 0) ? (uint32_t)p : 0;
if (unp_lim > 0 && inst_power < (uint32_t)unp_lim)
{
ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
inst_power, (uint32_t)unp_lim);
reached = true;
}
if (reached)
evse_set_limit_reached(true);
}

View File

@@ -4,121 +4,375 @@
#include "evse_hardware.h"
#include "evse_config.h"
#include "evse_api.h"
#include "auth.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_err.h"
#include <string.h>
#include <inttypes.h>
#include "auth_events.h"
#include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "scheduler_events.h"
static const char *TAG = "EVSE_Manager";
static TickType_t auth_expiration = 0;
static SemaphoreHandle_t evse_mutex;
static QueueHandle_t auth_event_queue = NULL;
// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false;
static bool lb_prev_authorized = false;
// Estado de janela do scheduler
static bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
// ===== Task de ciclo principal =====
static void evse_manager_task(void *arg) {
while (true) {
static void lb_clear_pause_state(void)
{
portENTER_CRITICAL(&s_mgr_mux);
lb_paused = false;
lb_prev_authorized = false;
portEXIT_CRITICAL(&s_mgr_mux);
}
bool evse_sched_is_allowed(void)
{
bool v;
portENTER_CRITICAL(&s_sched_mux);
v = s_sched_allowed;
portEXIT_CRITICAL(&s_sched_mux);
return v;
}
static void evse_manager_handle_auth_on_tick(void)
{
bool sched_allowed = evse_sched_is_allowed();
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
bool local_auth_enabled;
bool local_lb_paused;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
local_lb_paused = lb_paused;
portEXIT_CRITICAL(&s_mgr_mux);
if (local_auth_enabled)
{
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
{
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
evse_state_set_authorized(false);
lb_clear_pause_state();
}
if (has_error && evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
err_bits);
evse_state_set_authorized(false);
lb_clear_pause_state();
}
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_state_set_authorized(false);
}
}
else
{
bool limit_hit = evse_is_limit_reached();
bool can_operate = evse_config_is_available() && evse_config_is_enabled();
if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
(int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
evse_state_set_authorized(false);
}
if (!local_lb_paused && sched_allowed && can_operate &&
!has_error && !limit_hit &&
!evse_state_get_authorized())
{
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
lb_clear_pause_state();
}
}
}
static void evse_manager_task(void *arg)
{
(void)arg;
while (true)
{
evse_manager_tick();
vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS));
}
}
static void evse_auth_event_task(void *arg) {
auth_event_t evt;
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != AUTH_EVENTS || !data) return;
while (true) {
if (xQueueReceive(auth_event_queue, &evt, portMAX_DELAY)) {
ESP_LOGI(TAG, "Evento de autenticação recebido: %s (%s)",
evt.tag, evt.authorized ? "AUTORIZADO" : "NEGADO");
switch (id)
{
case AUTH_EVENT_TAG_PROCESSED:
{
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
evse_state_set_authorized(evt->authorized);
lb_clear_pause_state();
break;
}
if (evt.authorized) {
evse_authorize();
auth_expiration = xTaskGetTickCount() + pdMS_TO_TICKS(2 * 60 * 1000); // 2 minutos
} else {
evse_manager_set_authorized(false);
ESP_LOGW(TAG, "Tag inválida, carregamento negado.");
case AUTH_EVENT_MODE_CHANGED:
case AUTH_EVENT_INIT:
{
const auth_mode_event_data_t *evt = (const auth_mode_event_data_t *)data;
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(evt->mode));
portENTER_CRITICAL(&s_mgr_mux);
auth_enabled = (evt->mode != AUTH_MODE_OPEN);
portEXIT_CRITICAL(&s_mgr_mux);
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
}
}
}
static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
(void)handler_arg;
(void)event_base;
if (!event_data) return;
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED",
(long long)evt->timestamp_us);
}
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{
const loadbalancer_master_limit_event_t *evt =
(const loadbalancer_master_limit_event_t *)event_data;
ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)",
evt->max_current, (long long)evt->timestamp_us);
if (evt->max_current == 0)
{
bool prev_auth = evse_state_get_authorized();
portENTER_CRITICAL(&s_mgr_mux);
lb_paused = true;
lb_prev_authorized = prev_auth;
portEXIT_CRITICAL(&s_mgr_mux);
if (prev_auth)
{
ESP_LOGI(TAG, "[LB] limit=0A → pausando sessão (authorized=false)");
evse_state_set_authorized(false);
}
}
else
{
evse_set_runtime_charging_current(evt->max_current);
bool was_paused;
bool prev_auth;
portENTER_CRITICAL(&s_mgr_mux);
was_paused = lb_paused;
prev_auth = lb_prev_authorized;
portEXIT_CRITICAL(&s_mgr_mux);
if (was_paused)
{
bool can_resume =
(evse_get_error() == 0) &&
evse_config_is_available() &&
evse_config_is_enabled() &&
evse_sched_is_allowed() &&
!evse_is_limit_reached();
if (!can_resume)
{
ESP_LOGW(TAG,
"[LB] limit=%uA → não retoma automaticamente (erro/indisp/desab/fora de horário/limite)",
evt->max_current);
lb_clear_pause_state();
return;
}
bool local_auth_enabled;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
lb_paused = false; // já vai tentar retomar
portEXIT_CRITICAL(&s_mgr_mux);
if (!local_auth_enabled)
{
ESP_LOGI(TAG, "[LB] limit=%uA → modo OPEN, reautorizando", evt->max_current);
evse_state_set_authorized(true);
}
else
{
if (prev_auth)
{
ESP_LOGI(TAG, "[LB] limit=%uA → RFID/OCPP, retomando autorização anterior", evt->max_current);
evse_state_set_authorized(true);
}
}
portENTER_CRITICAL(&s_mgr_mux);
lb_prev_authorized = false;
portEXIT_CRITICAL(&s_mgr_mux);
}
}
}
}
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != OCPP_EVENTS) return;
// ===== Inicialização dos módulos do EVSE =====
void evse_manager_init(void) {
switch (id)
{
case OCPP_EVENT_AUTHORIZED:
ESP_LOGI(TAG, "[OCPP] Authorized");
evse_state_set_authorized(true);
lb_clear_pause_state();
break;
case OCPP_EVENT_AUTH_REJECTED:
case OCPP_EVENT_AUTH_TIMEOUT:
case OCPP_EVENT_REMOTE_STOP:
case OCPP_EVENT_STOP_TX:
ESP_LOGW(TAG, "[OCPP] Authorization/Stop");
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
case OCPP_EVENT_REMOTE_START:
ESP_LOGI(TAG, "[OCPP] RemoteStart");
evse_state_set_authorized(true);
lb_clear_pause_state();
break;
case OCPP_EVENT_START_TX:
ESP_LOGI(TAG, "[OCPP] StartTx");
lb_clear_pause_state();
break;
case OCPP_EVENT_OPERATIVE_UPDATED:
{
if (!data)
{
ESP_LOGW(TAG, "[OCPP] OperativeUpdated sem payload — ignorado");
break;
}
const ocpp_operative_event_t *ev = (const ocpp_operative_event_t *)data;
ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld",
(int)ev->operative, (long long)ev->timestamp_us);
evse_config_set_enabled(ev->operative);
break;
}
default:
ESP_LOGD(TAG, "[OCPP] Unhandled event id=%" PRId32, id);
break;
}
}
static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != SCHED_EVENTS || data == NULL) return;
const sched_event_state_t *ev = (const sched_event_state_t *)data;
portENTER_CRITICAL(&s_sched_mux);
s_sched_allowed = ev->allowed_now;
portEXIT_CRITICAL(&s_sched_mux);
ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now);
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false);
}
}
void evse_manager_init(void)
{
evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL);
esp_err_t err = evse_config_init();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to init EVSE config NVS: %s", esp_err_to_name(err));
}
evse_config_init();
evse_error_init();
evse_hardware_init();
evse_state_init();
evse_meter_init();
evse_session_init();
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));
ESP_LOGI(TAG, "EVSE Manager inicializado.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 8192, NULL, 4, NULL);
configASSERT(rc == pdPASS);
}
// ===== Inicia processamento de eventos de autenticação =====
void evse_manager_start(QueueHandle_t queue) {
auth_event_queue = queue;
xTaskCreate(evse_auth_event_task, "evse_auth_evt", 4096, NULL, 5, NULL);
}
void evse_manager_tick(void) {
void evse_manager_tick(void)
{
xSemaphoreTake(evse_mutex, portMAX_DELAY);
evse_hardware_tick();
evse_error_tick();
evse_state_tick();
evse_temperature_check();
evse_session_tick();
// Verifica expiração de autorização somente se auth está habilitado
if (auth_is_enabled()) {
if (evse_state_get_authorized() && auth_expiration > 0 &&
xTaskGetTickCount() >= auth_expiration) {
ESP_LOGI(TAG, "Autorização expirada após 2 minutos.");
evse_state_set_authorized(false);
auth_expiration = 0;
}
} else {
// Se autenticação não é necessária, sempre considera autorizado
if (!evse_state_get_authorized()) {
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Autenticação desativada: autorização forçada.");
}
}
evse_manager_handle_auth_on_tick();
xSemaphoreGive(evse_mutex);
}
// ===== Controles e status =====
bool evse_manager_is_available(void) {
return evse_config_is_available();
}
void evse_manager_set_available(bool available) {
evse_config_set_available(available);
}
void evse_manager_set_authorized(bool authorized) {
evse_state_set_authorized(authorized);
}
bool evse_manager_is_charging(void) {
return evse_state_is_charging(evse_get_state());
}
void evse_manager_set_enabled(bool enabled) {
evse_config_set_enabled(enabled);
}
bool evse_manager_is_enabled(void) {
return evse_config_is_enabled();
}

View File

@@ -0,0 +1,121 @@
#include "evse_meter.h"
#include "meter_events.h"
#include "esp_event.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include <string.h>
#include <inttypes.h>
static const char *TAG = "evse_meter";
static SemaphoreHandle_t meter_mutex;
typedef struct
{
uint32_t power_watts[EVSE_METER_PHASE_COUNT];
float voltage[EVSE_METER_PHASE_COUNT];
float current[EVSE_METER_PHASE_COUNT];
uint32_t energy_wh;
} evse_meter_data_t;
static evse_meter_data_t meter_data;
static void on_meter_event_dispatcher(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base == METER_EVENT && id == METER_EVENT_DATA_READY && data)
{
const meter_event_data_t *evt = (const meter_event_data_t *)data;
if (strcmp(evt->source, "EVSE") == 0)
{
evse_meter_on_meter_event(arg, data);
}
}
}
void evse_meter_on_meter_event(void *arg, void *event_data)
{
const meter_event_data_t *evt = (const meter_event_data_t *)event_data;
if (!evt)
return;
xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
meter_data.power_watts[i] = evt->watt[i];
meter_data.voltage[i] = evt->vrms[i];
meter_data.current[i] = evt->irms[i];
}
meter_data.energy_wh = (uint32_t)(evt->total_energy);
xSemaphoreGive(meter_mutex);
ESP_LOGD(TAG,
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, "
"total_energy=%" PRIu32 "Wh",
meter_data.power_watts[0], meter_data.power_watts[1], meter_data.power_watts[2],
meter_data.voltage[0], meter_data.voltage[1], meter_data.voltage[2],
meter_data.current[0], meter_data.current[1], meter_data.current[2],
meter_data.energy_wh);
}
void evse_meter_init(void)
{
meter_mutex = xSemaphoreCreateMutex();
ESP_ERROR_CHECK(meter_mutex ? ESP_OK : ESP_FAIL);
ESP_ERROR_CHECK(esp_event_handler_register(
METER_EVENT, METER_EVENT_DATA_READY,
on_meter_event_dispatcher, NULL));
memset(&meter_data, 0, sizeof(meter_data));
ESP_LOGI(TAG, "EVSE Meter listener registered.");
}
int evse_meter_get_instant_power(void)
{
xSemaphoreTake(meter_mutex, portMAX_DELAY);
int sum = 0;
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
sum += meter_data.power_watts[i];
}
xSemaphoreGive(meter_mutex);
return sum;
}
int evse_meter_get_total_energy(void)
{
xSemaphoreTake(meter_mutex, portMAX_DELAY);
int val = meter_data.energy_wh;
xSemaphoreGive(meter_mutex);
return val;
}
void evse_meter_get_power(int power[EVSE_METER_PHASE_COUNT])
{
xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
power[i] = meter_data.power_watts[i];
}
xSemaphoreGive(meter_mutex);
}
void evse_meter_get_voltage(float voltage[EVSE_METER_PHASE_COUNT])
{
xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
voltage[i] = meter_data.voltage[i];
}
xSemaphoreGive(meter_mutex);
}
void evse_meter_get_current(float current[EVSE_METER_PHASE_COUNT])
{
xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
current[i] = meter_data.current[i];
}
xSemaphoreGive(meter_mutex);
}

243
components/evse/evse_pilot.c Executable file
View File

@@ -0,0 +1,243 @@
// components/evse/evse_pilot.c
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_rom_sys.h"
#include "evse_pilot.h"
#include "adc121s021_dma.h"
#include "board_config.h"
#define PILOT_PWM_TIMER LEDC_TIMER_0
#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0
#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE
#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT
#define PILOT_PWM_MAX_DUTY 1023
// --- Configuração de amostragem do Pilot ---
#define NUM_PILOT_SAMPLES 100
#define MAX_SAMPLE_ATTEMPTS 1000
#define PILOT_SAMPLE_DELAY_US 10
// Percentagem para descartar extremos superior/inferior (ruído)
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
// ADC referência
#define ADC121_VREF_MV 3300
#define ADC121_MAX 4095
static const char *TAG = "evse_pilot";
typedef enum {
PILOT_MODE_DC_HIGH = 0, // +12V (nível alto)
PILOT_MODE_DC_LOW, // nível baixo / pilot desligado (dependente do hardware)
PILOT_MODE_PWM // PWM ativo
} pilot_mode_t;
static pilot_mode_t s_mode = PILOT_MODE_DC_LOW;
static uint32_t last_pwm_duty = 0;
// ---------------------
// Helpers internos
// ---------------------
static int adc_raw_to_mv(uint16_t raw)
{
return (int)((raw * ADC121_VREF_MV) / ADC121_MAX);
}
static int compare_uint16(const void *a, const void *b)
{
uint16_t va = *(const uint16_t *)a;
uint16_t vb = *(const uint16_t *)b;
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
// ---------------------
// Inicialização PWM + ADC
// ---------------------
void pilot_init(void)
{
// Configura timer do PWM do Pilot (1 kHz)
ledc_timer_config_t ledc_timer = {
.speed_mode = PILOT_PWM_SPEED_MODE,
.timer_num = PILOT_PWM_TIMER,
.duty_resolution = PILOT_PWM_DUTY_RES,
.freq_hz = 1000, // 1 kHz (IEC 61851)
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
// Canal do PWM no pino configurado em board_config
ledc_channel_config_t ledc_channel = {
.speed_mode = PILOT_PWM_SPEED_MODE,
.channel = PILOT_PWM_CHANNEL,
.timer_sel = PILOT_PWM_TIMER,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = board_config.pilot_pwm_gpio,
.duty = 0,
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
// Garante que começa parado e em idle baixo (pilot off)
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
s_mode = PILOT_MODE_DC_LOW;
last_pwm_duty = 0;
// Inicializa driver do ADC121S021
adc121s021_dma_init();
}
// ---------------------
// Controlo do modo do Pilot
// ---------------------
void pilot_set_level(bool high)
{
pilot_mode_t target = high ? PILOT_MODE_DC_HIGH : PILOT_MODE_DC_LOW;
// Se já estiver no modo DC desejado e sem PWM ativo, ignora
if (s_mode == target && last_pwm_duty == 0) {
return;
}
ESP_LOGI(TAG, "Pilot set DC level: %s", high ? "HIGH(+12V)" : "LOW/OFF");
// Para PWM e fixa o nível idle do GPIO
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, high ? 1 : 0));
s_mode = target;
last_pwm_duty = 0;
}
void pilot_set_amps(uint16_t amps)
{
if (amps < 6 || amps > 80)
{
ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 680 A)", amps);
return;
}
uint32_t duty_percent;
if (amps <= 51)
{
duty_percent = (amps * 10) / 6;
}
else
{
duty_percent = (amps * 10) / 25 + 64;
}
if (duty_percent > 100) duty_percent = 100;
uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100;
// Se já estiver em PWM com o mesmo duty, ignora
if (s_mode == PILOT_MODE_PWM && last_pwm_duty == duty) {
return;
}
s_mode = PILOT_MODE_PWM;
last_pwm_duty = duty;
ESP_LOGI(TAG, "Pilot set PWM: %d A → %d/%d (≈ %d%% duty)",
amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);
ESP_ERROR_CHECK(ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty));
ESP_ERROR_CHECK(ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL));
}
bool pilot_get_state(void)
{
// "Alto" significa DC +12V (estado A). PWM não conta como DC high.
return (s_mode == PILOT_MODE_DC_HIGH);
}
// ---------------------
// Medição do sinal de Pilot (PWM 1 kHz J1772)
// ---------------------
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
{
ESP_LOGD(TAG, "pilot_measure");
uint16_t samples[NUM_PILOT_SAMPLES];
int collected = 0;
int attempts = 0;
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS)
{
uint16_t adc_sample;
if (adc121s021_dma_get_sample(&adc_sample))
{
samples[collected++] = adc_sample;
esp_rom_delay_us(PILOT_SAMPLE_DELAY_US);
}
else
{
esp_rom_delay_us(100);
attempts++;
}
}
if (collected < NUM_PILOT_SAMPLES)
{
ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES);
*up_voltage = PILOT_VOLTAGE_1;
*down_voltage_n12 = false;
return;
}
// Ordena as amostras para eliminar extremos (ruído/espúrios)
qsort(samples, collected, sizeof(uint16_t), compare_uint16);
int k = (collected * PILOT_EXTREME_PERCENT) / 100;
if (k < 2) k = 2; // garante margem mínima
// descarta k/2 em cada lado (aprox. 10% total, mantendo simetria)
int low_index = k / 2;
int high_index = collected - 1 - (k / 2);
if (low_index < 0) low_index = 0;
if (high_index >= collected) high_index = collected - 1;
if (high_index <= low_index) high_index = low_index;
uint16_t low_raw = samples[low_index];
uint16_t high_raw = samples[high_index];
int high_mv = adc_raw_to_mv(high_raw);
int low_mv = adc_raw_to_mv(low_raw);
// Determina o nível positivo (+12, +9, +6, +3 ou <3 V)
if (high_mv >= board_config.pilot_down_threshold_12)
{
*up_voltage = PILOT_VOLTAGE_12;
}
else if (high_mv >= board_config.pilot_down_threshold_9)
{
*up_voltage = PILOT_VOLTAGE_9;
}
else if (high_mv >= board_config.pilot_down_threshold_6)
{
*up_voltage = PILOT_VOLTAGE_6;
}
else if (high_mv >= board_config.pilot_down_threshold_3)
{
*up_voltage = PILOT_VOLTAGE_3;
}
else
{
*up_voltage = PILOT_VOLTAGE_1;
}
// Verifica se o nível negativo atinge -12 V (diodo presente, C/D válidos)
*down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12);
ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d (high=%d mV, low=%d mV)",
*up_voltage, *down_voltage_n12, high_mv, low_mv);
}

View File

@@ -0,0 +1,222 @@
#include <inttypes.h>
#include "evse_session.h"
#include "evse_meter.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "evse_events.h"
#include "esp_event.h"
#include "evse_limits.h"
#include "esp_timer.h"
#define EVSE_EVENT_POST_TIMEOUT_MS 50
static const char *TAG = "evse_session";
static TickType_t session_start_tick = 0;
// Tempo real (microsegundos)
static int64_t session_start_us = 0;
static int64_t last_tick_us = 0;
// Energia integrada com tempo real: soma de (W * us)
static uint64_t watt_microseconds = 0;
static evse_session_t last_session;
static bool last_session_valid = false;
static uint32_t session_counter = 0;
static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
static void post_session_event(const evse_session_event_data_t *evt)
{
esp_err_t err = esp_event_post(
EVSE_EVENTS,
EVSE_EVENT_SESSION,
evt,
sizeof(*evt),
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_event_post(EVSE_EVENT_SESSION) failed: %s", esp_err_to_name(err));
}
}
void evse_session_init(void)
{
portENTER_CRITICAL(&session_mux);
session_start_tick = 0;
session_start_us = 0;
last_tick_us = 0;
watt_microseconds = 0;
last_session_valid = false;
session_counter = 0;
portEXIT_CRITICAL(&session_mux);
}
void evse_session_start(void)
{
TickType_t tick = xTaskGetTickCount();
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
session_start_tick = tick;
session_start_us = now_us;
last_tick_us = now_us;
watt_microseconds = 0;
session_counter++;
uint32_t id = session_counter;
portEXIT_CRITICAL(&session_mux);
evse_set_limit_reached(false);
ESP_LOGI(TAG, "Session started (id=%" PRIu32 ") tick=%u us=%" PRId64,
id, (unsigned)tick, now_us);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_STARTED,
.session_id = id,
.duration_s = 0,
.energy_wh = 0,
.avg_power_w = 0,
.is_current = true,
};
post_session_event(&evt);
}
void evse_session_end(void)
{
TickType_t start_tick;
int64_t start_us;
uint64_t w_us;
uint32_t id;
int64_t end_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
if (session_start_tick == 0)
{
portEXIT_CRITICAL(&session_mux);
ESP_LOGW(TAG, "evse_session_end called without active session");
return;
}
start_tick = session_start_tick;
start_us = session_start_us;
w_us = watt_microseconds;
id = session_counter;
session_start_tick = 0;
session_start_us = 0;
last_tick_us = 0;
watt_microseconds = 0;
portEXIT_CRITICAL(&session_mux);
uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0;
uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
portENTER_CRITICAL(&session_mux);
last_session.start_tick = start_tick;
last_session.duration_s = duration_s;
last_session.energy_wh = energy_wh;
last_session.avg_power_w = avg_power;
last_session.is_current = false;
last_session_valid = true;
portEXIT_CRITICAL(&session_mux);
ESP_LOGI(TAG,
"Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32
" Wh, avg_power=%" PRIu32 " W",
id, duration_s, energy_wh, avg_power);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_FINISHED,
.session_id = id,
.duration_s = duration_s,
.energy_wh = energy_wh,
.avg_power_w = avg_power,
.is_current = false,
};
post_session_event(&evt);
}
void evse_session_tick(void)
{
// Potência instantânea pode ser negativa (ruído/overflow de sensor) -> clamp
int p = evse_meter_get_instant_power();
uint32_t power_w = (p > 0) ? (uint32_t)p : 0;
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
if (session_start_tick != 0)
{
if (last_tick_us == 0)
{
last_tick_us = now_us;
}
int64_t dt_us = now_us - last_tick_us;
if (dt_us > 0)
{
// Energia incremental: W * us (64-bit)
watt_microseconds += ((uint64_t)power_w * (uint64_t)dt_us);
last_tick_us = now_us;
}
else
{
// relógio não devia andar para trás; ignora
last_tick_us = now_us;
}
}
portEXIT_CRITICAL(&session_mux);
}
bool evse_session_get(evse_session_t *out)
{
if (out == NULL)
return false;
TickType_t start_tick;
int64_t start_us;
uint64_t w_us;
bool has_current;
evse_session_t last_copy;
bool last_valid;
int64_t now_us = esp_timer_get_time();
portENTER_CRITICAL(&session_mux);
start_tick = session_start_tick;
start_us = session_start_us;
w_us = watt_microseconds;
has_current = (session_start_tick != 0);
last_copy = last_session;
last_valid = last_session_valid;
portEXIT_CRITICAL(&session_mux);
if (has_current)
{
uint32_t duration_s = (now_us > start_us) ? (uint32_t)((now_us - start_us) / 1000000LL) : 0;
uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
out->start_tick = start_tick;
out->duration_s = duration_s;
out->energy_wh = energy_wh;
out->avg_power_w = avg_power;
out->is_current = true;
return true;
}
if (last_valid)
{
*out = last_copy;
return true;
}
return false;
}

View File

@@ -1,73 +1,207 @@
#include "evse_api.h"
#include "evse_state.h"
#include "evse_session.h"
#include "evse_events.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "esp_log.h"
#include "esp_event.h"
#define EVSE_EVENT_POST_TIMEOUT_MS 50
static evse_state_t current_state = EVSE_STATE_A;
static bool is_authorized = false;
// Proteção básica para variáveis globais em sistemas concorrentes
static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED;
static const char *TAG = "evse_state";
void evse_set_state(evse_state_t state) {
portENTER_CRITICAL(&state_mux);
current_state = state;
portEXIT_CRITICAL(&state_mux);
static evse_state_event_t map_state_to_event(evse_state_t s)
{
switch (s)
{
case EVSE_STATE_A:
return EVSE_STATE_EVENT_IDLE;
case EVSE_STATE_B1:
case EVSE_STATE_B2:
return EVSE_STATE_EVENT_WAITING;
case EVSE_STATE_C1:
case EVSE_STATE_C2:
case EVSE_STATE_D1:
case EVSE_STATE_D2:
return EVSE_STATE_EVENT_CHARGING;
case EVSE_STATE_E:
case EVSE_STATE_F:
return EVSE_STATE_EVENT_FAULT;
default:
return EVSE_STATE_EVENT_IDLE;
}
}
evse_state_t evse_get_state(void) {
static void post_evse_event(evse_event_id_t id, const void *data, size_t len)
{
esp_err_t err = esp_event_post(
EVSE_EVENTS,
id,
data,
len,
portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_event_post(id=%d) failed: %s", (int)id, esp_err_to_name(err));
}
}
bool evse_state_is_charging(evse_state_t state)
{
// “charging” == energia efetiva (relé ON)
return (state == EVSE_STATE_C2 || state == EVSE_STATE_D2);
}
bool evse_state_is_power_flowing(evse_state_t state)
{
return evse_state_is_charging(state);
}
bool evse_state_is_requesting(evse_state_t state)
{
// EV pediu carga mas o relé ainda está OFF
return (state == EVSE_STATE_C1 || state == EVSE_STATE_D1);
}
bool evse_state_is_plugged(evse_state_t state)
{
return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 ||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2;
}
bool evse_state_is_session(evse_state_t state)
{
// Sessão lógica: “autorizado/pronto” ou “a pedir/a fornecer energia”
return (state == EVSE_STATE_B2 ||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2);
}
void evse_set_state(evse_state_t new_state)
{
bool changed = false;
evse_state_t prev_state;
bool start_session = false;
bool end_session = false;
portENTER_CRITICAL(&state_mux);
prev_state = current_state;
if (new_state != current_state)
{
// Sessão começa quando entra em energia (relé ON)
if (evse_state_is_power_flowing(new_state) && !evse_state_is_power_flowing(prev_state))
{
start_session = true;
}
// Sessão termina quando sai de energia
else if (!evse_state_is_power_flowing(new_state) && evse_state_is_power_flowing(prev_state))
{
end_session = true;
}
current_state = new_state;
changed = true;
}
portEXIT_CRITICAL(&state_mux);
// Fora da região crítica
if (start_session)
{
evse_session_start();
}
if (end_session)
{
evse_session_end();
}
if (changed)
{
ESP_LOGI(TAG, "State changed: %s → %s",
evse_state_to_str(prev_state),
evse_state_to_str(new_state));
evse_state_event_data_t evt = {
.state = map_state_to_event(new_state)};
post_evse_event(EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt));
}
}
evse_state_t evse_get_state(void)
{
portENTER_CRITICAL(&state_mux);
evse_state_t s = current_state;
portEXIT_CRITICAL(&state_mux);
return s;
}
const char* evse_state_to_str(evse_state_t state) {
switch (state) {
case EVSE_STATE_A: return "A - EV Not Connected (12V)";
case EVSE_STATE_B1: return "B1 - EV Connected (9V, Not Authorized)";
case EVSE_STATE_B2: return "B2 - EV Connected (9V, Authorized and Ready)";
case EVSE_STATE_C1: return "C1 - Charging Requested (6V, Relay Off)";
case EVSE_STATE_C2: return "C2 - Charging Active (6V, Relay On)";
case EVSE_STATE_D1: return "D1 - Ventilation Required (3V, Relay Off)";
case EVSE_STATE_D2: return "D2 - Ventilation Active (3V, Relay On)";
case EVSE_STATE_E: return "E - Error: Control Pilot Shorted to Ground (0V)";
case EVSE_STATE_F: return "F - Fault: EVSE Unavailable or No Pilot Signal";
default: return "Unknown State";
const char *evse_state_to_str(evse_state_t state)
{
switch (state)
{
case EVSE_STATE_A:
return "A - EV Not Connected (12V)";
case EVSE_STATE_B1:
return "B1 - EV Connected (9V, Not Authorized)";
case EVSE_STATE_B2:
return "B2 - EV Connected (9V, Authorized and Ready)";
case EVSE_STATE_C1:
return "C1 - Charging Requested (6V, Relay Off)";
case EVSE_STATE_C2:
return "C2 - Charging Active (6V, Relay On)";
case EVSE_STATE_D1:
return "D1 - Ventilation Required (3V, Relay Off)";
case EVSE_STATE_D2:
return "D2 - Ventilation Active (3V, Relay On)";
case EVSE_STATE_E:
return "E - Error: Control Pilot Shorted to Ground (0V)";
case EVSE_STATE_F:
return "F - Fault: EVSE Unavailable or No Pilot Signal";
default:
return "Unknown State";
}
}
void evse_state_init(void) {
void evse_state_init(void)
{
portENTER_CRITICAL(&state_mux);
current_state = EVSE_STATE_A;
is_authorized = false;
portEXIT_CRITICAL(&state_mux);
ESP_LOGI(TAG, "Initialized in state: %s", evse_state_to_str(current_state));
evse_state_event_data_t evt = {
.state = map_state_to_event(current_state)};
post_evse_event(EVSE_EVENT_INIT, &evt, sizeof(evt));
}
void evse_state_tick(void) {
// Tick do estado (placeholder)
void evse_state_tick(void)
{
// placeholder
}
bool evse_state_is_charging(evse_state_t state) {
return state == EVSE_STATE_C1 || state == EVSE_STATE_C2;
}
bool evse_state_is_plugged(evse_state_t state) {
return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 ||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2;
}
bool evse_state_is_session(evse_state_t state) {
return state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2;
}
void evse_state_set_authorized(bool authorized) {
void evse_state_set_authorized(bool authorized)
{
portENTER_CRITICAL(&state_mux);
is_authorized = authorized;
portEXIT_CRITICAL(&state_mux);
}
bool evse_state_get_authorized(void) {
bool evse_state_get_authorized(void)
{
portENTER_CRITICAL(&state_mux);
bool result = is_authorized;
portEXIT_CRITICAL(&state_mux);

View File

@@ -3,68 +3,104 @@
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "evse_state.h" // Define evse_state_t
#include "evse_state.h"
#include "freertos/FreeRTOS.h"
#include "evse_session.h"
// Inicialização
void evse_init(void);
void evse_process(void);
// Estado
#ifdef __cplusplus
extern "C" {
#endif
// ===============================
// Core EVSE State
// ===============================
/**
* @brief Get current EVSE state (e.g., A, B1, C2).
*/
evse_state_t evse_get_state(void);
const char* evse_state_to_str(evse_state_t state);
bool evse_is_connector_plugged(evse_state_t state);
/**
* @brief Set the EVSE state (e.g., called by FSM or hardware layer).
*/
void evse_set_state(evse_state_t state);
// ===============================
// Charging Session Info
// ===============================
/**
* @brief Retrieve statistics of either the current ongoing session (if any)
* or the last completed session.
* @param out pointer to evse_session_t to be filled
* @return true if there is a current or last session available
*/
bool evse_get_session(evse_session_t *out);
// ===============================
// Charging Session Info
// ===============================
/**
* @brief Returns true if the EV is charging (C1 or C2).
*/
bool evse_state_is_charging(evse_state_t state);
/**
* @brief Returns true if the EV is connected (plugged).
*/
bool evse_state_is_plugged(evse_state_t state);
/**
* @brief Returns true if a charging session is active (B2, C1, C2).
*/
bool evse_state_is_session(evse_state_t state);
// ===============================
// Authorization
// ===============================
/**
* @brief Set whether the vehicle is authorized to charge.
*/
void evse_state_set_authorized(bool authorized);
/**
* @brief Get current authorization status.
*/
bool evse_state_get_authorized(void);
// ===============================
// Configuration / Availability
// ===============================
/**
* @brief Enable or disable the EVSE (software flag, persisted in NVS).
*/
void evse_set_enabled(bool value);
/**
* @brief Returns true if the EVSE is currently available for use.
*/
bool evse_is_available(void);
/**
* @brief Set EVSE availability flag (may be persisted in NVS).
*/
void evse_set_available(bool value);
// ===============================
// Limit Status
// ===============================
/**
* @brief Returns true if any runtime charging limit has been reached.
*/
bool evse_is_limit_reached(void);
// Autorização e disponibilidade
bool evse_is_enabled(void);
void evse_set_enabled(bool value);
bool evse_is_available(void);
void evse_set_available(bool value);
bool evse_is_require_auth(void);
void evse_set_require_auth(bool value);
void evse_authorize(void);
bool evse_is_pending_auth(void);
void evse_set_authorized(bool value);
// Corrente
uint16_t evse_get_charging_current(void);
esp_err_t evse_set_charging_current(uint16_t value);
uint16_t evse_get_default_charging_current(void);
esp_err_t evse_set_default_charging_current(uint16_t value);
uint8_t evse_get_max_charging_current(void);
esp_err_t evse_set_max_charging_current(uint8_t value);
// Grid
uint8_t grid_get_max_current(void);
esp_err_t grid_set_max_current(uint8_t value);
// Temperatura
uint8_t evse_get_temp_threshold(void);
esp_err_t evse_set_temp_threshold(uint8_t value);
// RCM / Socket
bool evse_get_socket_outlet(void);
esp_err_t evse_set_socket_outlet(bool value);
bool evse_is_rcm(void);
esp_err_t evse_set_rcm(bool value);
// Limites
uint32_t evse_get_consumption_limit(void);
void evse_set_consumption_limit(uint32_t value);
uint32_t evse_get_charging_time_limit(void);
void evse_set_charging_time_limit(uint32_t value);
uint16_t evse_get_under_power_limit(void);
void evse_set_under_power_limit(uint16_t value);
void evse_set_limit_reached(uint8_t value);
// Limites default
uint32_t evse_get_default_consumption_limit(void);
void evse_set_default_consumption_limit(uint32_t value);
uint32_t evse_get_default_charging_time_limit(void);
void evse_set_default_charging_time_limit(uint32_t value);
uint16_t evse_get_default_under_power_limit(void);
void evse_set_default_under_power_limit(uint16_t value);
#ifdef __cplusplus
}
#endif
#endif // EVSE_API_H

View File

@@ -4,6 +4,8 @@
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "evse_events.h"
#ifdef __cplusplus
extern "C" {
@@ -13,15 +15,9 @@ extern "C" {
// Limites Globais (Defines)
// ========================
// Corrente máxima de carregamento (configurável pelo usuário)
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
// Corrente máxima da rede elétrica (grid)
#define MIN_GRID_CURRENT_LIMIT 6 // A
#define MAX_GRID_CURRENT_LIMIT 100 // A
// Corrente via cabo (proximity) — se configurável
#define MIN_CABLE_CURRENT_LIMIT 6 // A
#define MAX_CABLE_CURRENT_LIMIT 63 // A
@@ -33,21 +29,18 @@ extern "C" {
esp_err_t evse_config_init(void);
void evse_check_defaults(void);
// Corrente de carregamento
// Corrente máxima de hardware (fixa)
uint8_t evse_get_max_charging_current(void);
esp_err_t evse_set_max_charging_current(uint8_t value);
// Corrente configurável (persistida) <= max hardware
uint16_t evse_get_charging_current(void);
esp_err_t evse_set_charging_current(uint16_t value);
uint16_t evse_get_default_charging_current(void);
esp_err_t evse_set_default_charging_current(uint16_t value);
// Corrente runtime (RAM) <= max hardware (load balancer pode alterar)
void evse_set_runtime_charging_current(uint16_t value);
uint16_t evse_get_runtime_charging_current(void);
// Corrente da rede elétrica
uint8_t grid_get_max_current(void);
esp_err_t grid_set_max_current(uint8_t value);
// Configuração de socket outlet
// Socket outlet
bool evse_get_socket_outlet(void);
esp_err_t evse_set_socket_outlet(bool socket_outlet);
@@ -59,10 +52,6 @@ esp_err_t evse_set_rcm(bool rcm);
uint8_t evse_get_temp_threshold(void);
esp_err_t evse_set_temp_threshold(uint8_t threshold);
// Autenticação
bool evse_is_require_auth(void);
void evse_set_require_auth(bool require);
// Disponibilidade
bool evse_config_is_available(void);
void evse_config_set_available(bool available);

View File

@@ -0,0 +1,17 @@
#ifndef EVSE_CORE_H
#define EVSE_CORE_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initializes the EVSE system and starts core task loop.
*/
void evse_init(void);
#ifdef __cplusplus
}
#endif
#endif // EVSE_CORE_H

View File

@@ -3,13 +3,15 @@
#include <stdint.h>
#include <stdbool.h>
#include "pilot.h"
#include "evse_pilot.h"
#define EVSE_ERR_AUTO_CLEAR_BITS ( \
EVSE_ERR_DIODE_SHORT_BIT | \
EVSE_ERR_TEMPERATURE_HIGH_BIT | \
EVSE_ERR_RCM_TRIGGERED_BIT )
// ----------------------------------------------------
// Holdoff interno pós-erro (sem expor "cooldown" ao resto)
// ----------------------------------------------------
// Após TODOS os erros reais desaparecerem (raw_bits == 0),
// o módulo mantém o erro "visível" durante este tempo.
// Durante este período, evse_get_error() continua != 0.
#define EVSE_ERROR_COOLDOWN_MS 60000
// Error bits
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
@@ -26,20 +28,22 @@ void evse_error_init(void);
// Verificações e monitoramento
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v);
void evse_temperature_check(void);
void evse_error_tick(void);
// Leitura e controle de erros
// Leitura e controle de erros (estado "visível" com holdoff)
uint32_t evse_get_error(void);
bool evse_is_error_cleared(void);
void evse_mark_error_cleared(void);
void evse_error_set(uint32_t bitmask);
void evse_error_clear(uint32_t bitmask);
bool evse_error_is_active(void);
uint32_t evse_error_get_bits(void);
void evse_error_reset_flag(void);
// ----------------------------------------------------
// Semântica sticky: flag "todos erros limpos"
// (fica true quando o erro visível chega a 0; pode ser útil para UI/logs)
// ----------------------------------------------------
bool evse_error_cleared_flag(void);
void evse_error_reset_flag(void);
#endif // EVSE_ERROR_H

View File

@@ -1,33 +1,84 @@
#ifndef EVSE_EVENTS_H
#define EVSE_EVENTS_H
#include "evse_api.h"
#include "esp_event_base.h"
#pragma once
// Certifique-se de que ESP_EVENT_DECLARE_BASE seja corretamente reconhecido
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include "esp_event.h"
// Declaração da base de eventos EVSE (será definida em evse_events.c)
ESP_EVENT_DECLARE_BASE(EVSE_EVENT);
ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
typedef enum {
EVSE_EVENT_INIT,
EVSE_EVENT_STATE_CHANGED,
EVSE_EVENT_ERROR,
EVSE_EVENT_ERROR_CLEARED,
EVSE_EVENT_LIMIT_REACHED,
EVSE_EVENT_AUTH_GRANTED
EVSE_EVENT_CONFIG_UPDATED,
EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED,
EVSE_EVENT_SESSION,
EVSE_EVENT_ERROR_CHANGED,
} evse_event_id_t;
// Estrutura do evento de mudança de estado
typedef struct {
evse_state_t previous;
evse_state_t current;
} evse_event_state_changed_t;
// -----------------
// Eventos de STATE
// -----------------
typedef enum {
EVSE_STATE_EVENT_IDLE,
EVSE_STATE_EVENT_WAITING,
EVSE_STATE_EVENT_CHARGING,
EVSE_STATE_EVENT_FAULT
} evse_state_event_t;
#ifdef __cplusplus
}
#endif
typedef struct {
evse_state_event_t state;
} evse_state_event_data_t;
// -----------------
// Eventos de SESSÃO
// -----------------
typedef enum {
EVSE_SESSION_EVENT_STARTED = 0,
EVSE_SESSION_EVENT_FINISHED,
} evse_session_event_type_t;
typedef struct {
evse_session_event_type_t type; ///< STARTED / FINISHED
uint32_t session_id;
uint32_t duration_s;
uint32_t energy_wh;
uint32_t avg_power_w;
bool is_current;
} evse_session_event_data_t;
// -----------------
// Eventos de CONFIG
// -----------------
typedef struct {
bool charging;
float hw_max_current;
float runtime_current;
int64_t timestamp_us;
} evse_config_event_data_t;
typedef struct {
bool enabled;
int64_t timestamp_us;
} evse_enable_event_data_t;
typedef struct {
bool available;
int64_t timestamp_us;
} evse_available_event_data_t;
// -----------------
// Eventos de ERRO
// -----------------
typedef struct {
uint32_t error_bits; ///< estado atual (todos os bits de erro)
uint32_t changed_mask; ///< bits que mudaram nesta notificação
int64_t timestamp_us; ///< esp_timer_get_time()
} evse_error_event_data_t;
#endif // EVSE_EVENTS_H

View File

@@ -4,7 +4,7 @@
#include <stdint.h>
#include <stdbool.h>
#include "evse_api.h"
#include "pilot.h"
#include "evse_pilot.h"
#include "freertos/FreeRTOS.h"
#ifdef __cplusplus

View File

@@ -1,3 +1,4 @@
// === Início de: components/evse/include/evse_limits.h ===
#ifndef EVSE_LIMITS_H
#define EVSE_LIMITS_H
@@ -9,35 +10,56 @@
extern "C" {
#endif
/// Estado dos limites
void evse_set_limit_reached(uint8_t value);
// ============================
// Limit Status & Evaluation
// ============================
/**
* @brief Sets the internal 'limit reached' flag.
*/
void evse_set_limit_reached(bool value);
/**
* @brief Returns true if any runtime charging limit has been reached.
*/
bool evse_get_limit_reached(void);
/**
* @brief Convenience alias for evse_get_limit_reached().
*/
bool evse_is_limit_reached(void);
/// Verifica e aplica lógica de limites com base no estado atual do EVSE
void evse_limits_check(evse_state_t state);
/**
* @brief Checks if any session limit has been exceeded (energy, time or power).
* Should be called periodically during charging.
*/
void evse_limits_check(void);
/// Limites ativos (runtime)
// ============================
// Runtime Limit Configuration
// ============================
/**
* @brief Get/set energy consumption limit (in Wh).
*/
uint32_t evse_get_consumption_limit(void);
void evse_set_consumption_limit(uint32_t value);
/**
* @brief Get/set maximum charging time (in seconds).
*/
uint32_t evse_get_charging_time_limit(void);
void evse_set_charging_time_limit(uint32_t value);
/**
* @brief Get/set minimum acceptable power level (in Watts).
*/
uint16_t evse_get_under_power_limit(void);
void evse_set_under_power_limit(uint16_t value);
/// Limites padrão (persistentes)
uint32_t evse_get_default_consumption_limit(void);
void evse_set_default_consumption_limit(uint32_t value);
uint32_t evse_get_default_charging_time_limit(void);
void evse_set_default_charging_time_limit(uint32_t value);
uint16_t evse_get_default_under_power_limit(void);
void evse_set_default_under_power_limit(uint16_t value);
#ifdef __cplusplus
}
#endif
#endif // EVSE_LIMITS_H
// === Fim de: components/evse/include/evse_limits.h ===

Some files were not shown because too many files have changed in this diff Show More