diff --git a/applications/applications.h b/applications/applications.h index 8c9018e0..8c5c7e96 100644 --- a/applications/applications.h +++ b/applications/applications.h @@ -35,6 +35,7 @@ void power_task(void* p); void sd_card_test(void* p); void application_vibro(void* p); void app_gpio_test(void* p); +void app_ibutton(void* p); const FlipperStartupApp FLIPPER_STARTUP[] = { #ifdef APP_DISPLAY @@ -122,6 +123,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { .name = "gpio test", .libs = {1, FURI_LIB{"gui_task"}}, #endif + +#ifdef APP_IBUTTON + {.app = app_ibutton, .name = "ibutton", .libs = {1, FURI_LIB{"gui_task"}}}, +#endif }; const FlipperStartupApp FLIPPER_APPS[] = { @@ -164,4 +169,8 @@ const FlipperStartupApp FLIPPER_APPS[] = { #ifdef BUILD_GPIO_DEMO {.app = app_gpio_test, .name = "gpio test", .libs = {1, FURI_LIB{"gui_task"}}}, #endif + +#ifdef BUILD_IBUTTON + {.app = app_ibutton, .name = "ibutton", .libs = {1, FURI_LIB{"gui_task"}}}, +#endif }; \ No newline at end of file diff --git a/applications/ibutton/ibutton.cpp b/applications/ibutton/ibutton.cpp new file mode 100644 index 00000000..ea7cdd41 --- /dev/null +++ b/applications/ibutton/ibutton.cpp @@ -0,0 +1,101 @@ +#include "ibutton.h" +#include "ibutton_mode_dallas_read.h" +#include "ibutton_mode_dallas_emulate.h" + +// start app +void AppiButton::run() { + mode[0] = new AppiButtonModeDallasRead(this); + mode[1] = new AppiButtonModeDallasEmulate(this); + + // create pin + GpioPin red_led = led_gpio[0]; + GpioPin green_led = led_gpio[1]; + + // TODO open record + red_led_record = &red_led; + green_led_record = &green_led; + + // configure pin + gpio_init(red_led_record, GpioModeOutputOpenDrain); + gpio_init(green_led_record, GpioModeOutputOpenDrain); + + AppiButtonEvent event; + while(1) { + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); + + if(event_status == osOK) { + if(event.type == AppiButtonEvent::EventTypeKey) { + // press events + if(event.value.input.state && event.value.input.input == InputBack) { + printf("[ibutton] bye!\n"); + // TODO remove all widgets create by app + widget_enabled_set(widget, false); + furiac_exit(NULL); + } + + if(event.value.input.state && event.value.input.input == InputLeft) { + decrease_mode(); + } + + if(event.value.input.state && event.value.input.input == InputRight) { + increase_mode(); + } + } + } else { + event.type = AppiButtonEvent::EventTypeTick; + } + + acquire_state(); + mode[state.mode_index]->event(&event, &state); + release_state(); + + widget_update(widget); + }; +} + +// render app +void AppiButton::render(CanvasApi* canvas) { + canvas->set_color(canvas, ColorBlack); + canvas->set_font(canvas, FontPrimary); + canvas->draw_str(canvas, 2, 12, "iButton"); + + mode[state.mode_index]->render(canvas, &state); +} + +void AppiButton::blink_red() { + gpio_write(red_led_record, 0); + delay(10); + gpio_write(red_led_record, 1); +} + +void AppiButton::blink_green() { + gpio_write(green_led_record, 0); + delay(10); + gpio_write(green_led_record, 1); +} + +void AppiButton::increase_mode() { + acquire_state(); + if(state.mode_index < (modes_count - 1)) { + mode[state.mode_index]->release(); + state.mode_index++; + mode[state.mode_index]->acquire(); + } + release_state(); +} + +void AppiButton::decrease_mode() { + acquire_state(); + if(state.mode_index > 0) { + mode[state.mode_index]->release(); + state.mode_index--; + mode[state.mode_index]->acquire(); + } + release_state(); +} + +// app enter function +extern "C" void app_ibutton(void* p) { + AppiButton* app = new AppiButton(); + app->run(); +} \ No newline at end of file diff --git a/applications/ibutton/ibutton.h b/applications/ibutton/ibutton.h new file mode 100644 index 00000000..e3300987 --- /dev/null +++ b/applications/ibutton/ibutton.h @@ -0,0 +1,54 @@ +#pragma once +#include "app-template.h" +#include "ibutton_mode_template.h" + +// event enumeration type +typedef uint8_t event_t; + +class AppiButtonState { +public: + // state data + uint8_t dallas_address[8] = {0x01, 0xFD, 0x0E, 0x84, 0x01, 0x00, 0x00, 0xDB}; + uint8_t mode_index; + + // state initializer + AppiButtonState() { + mode_index = 0; + } +}; + +// events class +class AppiButtonEvent { +public: + // events enum + static const event_t EventTypeTick = 0; + static const event_t EventTypeKey = 1; + + // payload + union { + InputEvent input; + } value; + + // event type + event_t type; +}; + +// our app derived from base AppTemplate class +// with template variables +class AppiButton : public AppTemplate { +public: + GpioPin* red_led_record; + GpioPin* green_led_record; + + static const uint8_t modes_count = 2; + AppTemplateMode* mode[modes_count]; + + void run(); + void render(CanvasApi* canvas); + + void blink_red(); + void blink_green(); + + void increase_mode(); + void decrease_mode(); +}; \ No newline at end of file diff --git a/applications/ibutton/ibutton_mode_dallas_emulate.h b/applications/ibutton/ibutton_mode_dallas_emulate.h new file mode 100644 index 00000000..2d1fd9ed --- /dev/null +++ b/applications/ibutton/ibutton_mode_dallas_emulate.h @@ -0,0 +1,64 @@ +#pragma once +#include "ibutton.h" +#include "one_wire_slave_gpio.h" + +class AppiButtonModeDallasEmulate : public AppTemplateMode { +public: + const char* name = "dallas emulate"; + AppiButton* app; + OneWireGpioSlave* onewire_slave; + + void event(AppiButtonEvent* event, AppiButtonState* state); + void render(CanvasApi* canvas, AppiButtonState* state); + void acquire(); + void release(); + + AppiButtonModeDallasEmulate(AppiButton* parent_app) { + app = parent_app; + + // TODO open record + GpioPin one_wire_pin = {iBTN_GPIO_Port, iBTN_Pin}; + GpioPin* one_wire_pin_record = &one_wire_pin; + onewire_slave = new OneWireGpioSlave(one_wire_pin_record); + }; +}; + +void AppiButtonModeDallasEmulate::event(AppiButtonEvent* event, AppiButtonState* state) { + if(event->type == AppiButtonEvent::EventTypeTick) { + acquire(); + if(onewire_slave->emulate(state->dallas_address, 8)) { + app->blink_green(); + } else { + + } + } +} + +void AppiButtonModeDallasEmulate::render(CanvasApi* canvas, AppiButtonState* state) { + canvas->set_font(canvas, FontSecondary); + canvas->draw_str(canvas, 2, 25, "< dallas emulate"); + canvas->draw_str(canvas, 2, 37, "give me domophone"); + { + char buf[24]; + sprintf( + buf, + "%x:%x:%x:%x:%x:%x:%x:%x", + state->dallas_address[0], + state->dallas_address[1], + state->dallas_address[2], + state->dallas_address[3], + state->dallas_address[4], + state->dallas_address[5], + state->dallas_address[6], + state->dallas_address[7]); + canvas->draw_str(canvas, 2, 50, buf); + } +} + +void AppiButtonModeDallasEmulate::acquire() { + onewire_slave->start(); +} + +void AppiButtonModeDallasEmulate::release() { + onewire_slave->stop(); +} \ No newline at end of file diff --git a/applications/ibutton/ibutton_mode_dallas_read.h b/applications/ibutton/ibutton_mode_dallas_read.h new file mode 100644 index 00000000..8b6d5f2f --- /dev/null +++ b/applications/ibutton/ibutton_mode_dallas_read.h @@ -0,0 +1,119 @@ +#pragma once +#include "ibutton.h" +#include "one_wire_gpio.h" + +class AppiButtonModeDallasRead : public AppTemplateMode { +public: + const char* name = "dallas read"; + AppiButton* app; + OneWireGpio* onewire; + + void event(AppiButtonEvent* event, AppiButtonState* state); + void render(CanvasApi* canvas, AppiButtonState* state); + void acquire(); + void release(); + + AppiButtonModeDallasRead(AppiButton* parent_app) { + app = parent_app; + + // TODO open record + GpioPin one_wire_pin = {iBTN_GPIO_Port, iBTN_Pin}; + GpioPin* one_wire_pin_record = &one_wire_pin; + onewire = new OneWireGpio(one_wire_pin_record); + }; + + uint8_t crc_8(uint8_t* buffer, uint8_t count); +}; + +void AppiButtonModeDallasRead::event(AppiButtonEvent* event, AppiButtonState* state) { + if(event->type == AppiButtonEvent::EventTypeTick) { + bool result = 0; + uint8_t address[8]; + + osKernelLock(); + result = onewire->reset(); + osKernelUnlock(); + + if(result) { + printf("device on line\n"); + + delay(50); + osKernelLock(); + onewire->write(0x33); + onewire->read_bytes(address, 8); + osKernelUnlock(); + + printf("address: %x", address[0]); + for(uint8_t i = 1; i < 8; i++) { + printf(":%x", address[i]); + } + printf("\n"); + + printf("crc8: %x\n", crc_8(address, 7)); + + if(crc_8(address, 8) == 0) { + printf("CRC valid\n"); + memcpy(app->state.dallas_address, address, 8); + app->blink_green(); + } else { + printf("CRC invalid\n"); + } + } else { + } + } +} + +void AppiButtonModeDallasRead::render(CanvasApi* canvas, AppiButtonState* state) { + canvas->set_font(canvas, FontSecondary); + canvas->draw_str(canvas, 2, 25, "dallas read >"); + canvas->draw_str(canvas, 2, 37, "touch me, iButton"); + { + char buf[24]; + sprintf( + buf, + "%x:%x:%x:%x:%x:%x:%x:%x", + state->dallas_address[0], + state->dallas_address[1], + state->dallas_address[2], + state->dallas_address[3], + state->dallas_address[4], + state->dallas_address[5], + state->dallas_address[6], + state->dallas_address[7]); + canvas->draw_str(canvas, 2, 50, buf); + } +} + +uint8_t AppiButtonModeDallasRead::crc_8(uint8_t* buffer, uint8_t count) { + const uint8_t maxim_crc8_table[256] = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, 157, 195, + 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, 35, 125, 159, 193, + 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, 190, 224, 2, 92, 223, 129, + 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, 70, 24, 250, 164, 39, 121, 155, 197, + 132, 218, 56, 102, 229, 187, 89, 7, 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, + 165, 251, 120, 38, 196, 154, 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, + 198, 152, 122, 36, 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, + 231, 185, 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, + 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, 175, 241, + 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, 50, 108, 142, 208, + 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, 202, 148, 118, 40, 171, 245, + 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, 87, 9, 235, 181, 54, 104, 138, 212, + 149, 203, 41, 119, 244, 170, 72, 22, 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, + 151, 201, 74, 20, 246, 168, 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, + 215, 137, 107, 53}; + + uint8_t crc = 0; + + while(count--) { + crc = maxim_crc8_table[(crc ^ *buffer++)]; + } + return crc; +} + +void AppiButtonModeDallasRead::acquire() { + onewire->start(); +} + +void AppiButtonModeDallasRead::release() { + onewire->stop(); +} \ No newline at end of file diff --git a/applications/ibutton/ibutton_mode_template.h b/applications/ibutton/ibutton_mode_template.h new file mode 100644 index 00000000..001bd7e3 --- /dev/null +++ b/applications/ibutton/ibutton_mode_template.h @@ -0,0 +1,11 @@ +#pragma once + +// template for modes +template class AppTemplateMode { +public: + const char* name; + virtual void event(TEvents* event, TState* state) = 0; + virtual void render(CanvasApi* canvas, TState* state) = 0; + virtual void acquire() = 0; + virtual void release() = 0; +}; diff --git a/applications/ibutton/one_wire_gpio.h b/applications/ibutton/one_wire_gpio.h new file mode 100644 index 00000000..a99bbb02 --- /dev/null +++ b/applications/ibutton/one_wire_gpio.h @@ -0,0 +1,130 @@ +#pragma once +#include "flipper.h" +#include "flipper_v2.h" +#include "one_wire_timings.h" + +class OneWireGpio { +private: + GpioPin* gpio; + +public: + OneWireGpio(GpioPin* one_wire_gpio); + ~OneWireGpio(); + bool reset(void); + bool read_bit(void); + uint8_t read(void); + void read_bytes(uint8_t* buf, uint16_t count); + void write_bit(bool value); + void write(uint8_t value); + void start(void); + void stop(void); +}; + +OneWireGpio::OneWireGpio(GpioPin* one_wire_gpio) { + gpio = one_wire_gpio; +} + +OneWireGpio::~OneWireGpio() { + stop(); +} + +void OneWireGpio::start(void) { + gpio_init(gpio, GpioModeOutputOpenDrain); +} + +void OneWireGpio::stop(void) { + gpio_init(gpio, GpioModeAnalog); +} + +bool OneWireGpio::reset(void) { + uint8_t r; + uint8_t retries = 125; + + // wait until the gpio is high + gpio_write(gpio, true); + do { + if(--retries == 0) return 0; + delay_us(2); + } while(!gpio_read(gpio)); + + // pre delay + delay_us(OneWireTiming::RESET_DELAY_PRE); + + // drive low + gpio_write(gpio, false); + delay_us(OneWireTiming::RESET_DRIVE); + + // release + gpio_write(gpio, true); + delay_us(OneWireTiming::RESET_RELEASE); + + // read and post delay + r = !gpio_read(gpio); + delay_us(OneWireTiming::RESET_DELAY_POST); + + return r; +} + +bool OneWireGpio::read_bit(void) { + bool result; + + // drive low + gpio_write(gpio, false); + delay_us(OneWireTiming::READ_DRIVE); + + // release + gpio_write(gpio, true); + delay_us(OneWireTiming::READ_RELEASE); + + // read and post delay + result = gpio_read(gpio); + delay_us(OneWireTiming::READ_DELAY_POST); + + return result; +} + +void OneWireGpio::write_bit(bool value) { + if(value) { + // drive low + gpio_write(gpio, false); + delay_us(OneWireTiming::WRITE_1_DRIVE); + + // release + gpio_write(gpio, true); + delay_us(OneWireTiming::WRITE_1_RELEASE); + } else { + // drive low + gpio_write(gpio, false); + delay_us(OneWireTiming::WRITE_0_DRIVE); + + // release + gpio_write(gpio, true); + delay_us(OneWireTiming::WRITE_0_RELEASE); + } +} + +uint8_t OneWireGpio::read(void) { + uint8_t result = 0; + + for(uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) { + if(read_bit()) { + result |= bitMask; + } + } + + return result; +} + +void OneWireGpio::read_bytes(uint8_t* buffer, uint16_t count) { + for(uint16_t i = 0; i < count; i++) { + buffer[i] = read(); + } +} + +void OneWireGpio::write(uint8_t value) { + uint8_t bitMask; + + for(bitMask = 0x01; bitMask; bitMask <<= 1) { + write_bit((bitMask & value) ? 1 : 0); + } +} diff --git a/applications/ibutton/one_wire_slave_gpio.h b/applications/ibutton/one_wire_slave_gpio.h new file mode 100644 index 00000000..20ff2ca0 --- /dev/null +++ b/applications/ibutton/one_wire_slave_gpio.h @@ -0,0 +1,179 @@ +#pragma once +#include "flipper.h" +#include "flipper_v2.h" +#include "one_wire_timings.h" + +class OneWireGpioSlave { +private: + GpioPin* gpio; + +public: + OneWireGpioSlave(GpioPin* one_wire_gpio); + ~OneWireGpioSlave(); + void start(void); + void stop(void); + bool emulate(uint8_t* buffer, uint8_t length); + + bool check_reset(void); + bool show_presence(void); + bool receive_and_process_cmd(void); + bool receive(uint8_t* data, const uint8_t data_length); + bool receiveBit(void); + + bool overdrive_mode = false; + + OneWiteTimeType wait_while_gpio(volatile OneWiteTimeType retries, const bool pin_value); +}; + +OneWireGpioSlave::OneWireGpioSlave(GpioPin* one_wire_gpio) { + gpio = one_wire_gpio; +} + +OneWireGpioSlave::~OneWireGpioSlave() { + stop(); +} + +void OneWireGpioSlave::start(void) { + gpio_init(gpio, GpioModeOutputOpenDrain); +} + +void OneWireGpioSlave::stop(void) { + gpio_init(gpio, GpioModeAnalog); +} + +bool OneWireGpioSlave::emulate(uint8_t* buffer, uint8_t length) { + if(!check_reset()) { + printf("reset error\n"); + return false; + } + + if(!show_presence()) { + printf("presence error\n"); + return false; + } + + if(!receive_and_process_cmd()) { + printf("receive_and_process_cmd error\n"); + return false; + } + + printf("ok\n"); + return true; +} + +OneWiteTimeType OneWireGpioSlave::wait_while_gpio(OneWiteTimeType time, const bool pin_value) { + uint32_t start = DWT->CYCCNT; + uint32_t time_ticks = time * (SystemCoreClock / 1000000.0f); + + while(((DWT->CYCCNT - start) < time_ticks)) { + if(gpio_read(gpio) != pin_value) { + uint32_t time = (DWT->CYCCNT - start); + time /= (SystemCoreClock / 1000000.0f); + return time; + } + } + + return 0; +} + +bool OneWireGpioSlave::check_reset(void) { + while(gpio_read(gpio) == true) { + } + + /*if(wait_while_gpio(OneWireEmulateTiming::RESET_TIMEOUT * 20, true) == 0) { + printf("RESET_TIMEOUT\n"); + return false; + }*/ + + const OneWiteTimeType time_remaining = + wait_while_gpio(OneWireEmulateTiming::RESET_MAX[0], false); + + if(time_remaining == 0) { + return false; + } + + if(overdrive_mode && ((OneWireEmulateTiming::RESET_MAX[0] - + OneWireEmulateTiming::RESET_MIN[0]) <= time_remaining)) { + // normal reset detected + overdrive_mode = false; + }; + + bool result = (time_remaining <= OneWireEmulateTiming::RESET_MAX[0]) && + time_remaining >= OneWireEmulateTiming::RESET_MIN[overdrive_mode]; + + return result; +} + +bool OneWireGpioSlave::show_presence(void) { + wait_while_gpio(OneWireEmulateTiming::PRESENCE_TIMEOUT, true); + gpio_write(gpio, false); + delay_us(OneWireEmulateTiming::PRESENCE_MIN[overdrive_mode]); + gpio_write(gpio, true); + /*OneWiteTimeType wait_time = OneWireEmulateTiming::PRESENCE_MAX[overdrive_mode] - + OneWireEmulateTiming::PRESENCE_MIN[overdrive_mode]; + if(wait_while_gpio(wait_time, false) == 0) { + return false; + }*/ + + return true; +} + +bool OneWireGpioSlave::receive_and_process_cmd(void) { + uint8_t cmd; + receive(&cmd, 1); + printf("cmd %x\n", cmd); + return false; +} + +bool OneWireGpioSlave::receiveBit(void) { + // wait while bus is HIGH + OneWiteTimeType time = OneWireEmulateTiming::SLOT_MAX[overdrive_mode]; + time = wait_while_gpio(time, true); + if (time == 0) + { + printf("RESET_IN_PROGRESS\n"); + return false; + } + /*while ((DIRECT_READ(pin_baseReg, pin_bitMask) == 0) && (--retries != 0)); + if (retries == 0) + { + _error = Error::RESET_IN_PROGRESS; + return false; + }*/ + + // wait while bus is LOW + time = OneWireEmulateTiming::MSG_HIGH_TIMEOUT; + time = wait_while_gpio(time, false); + if (time == 0) + { + printf("TIMEOUT_HIGH\n"); + return false; + } + /*while ((DIRECT_READ(pin_baseReg, pin_bitMask) != 0) && (--retries != 0)); + if (retries == 0) + { + _error = Error::AWAIT_TIMESLOT_TIMEOUT_HIGH; + return false; + }*/ + + // wait a specific time to do a read (data is valid by then), // first difference to inner-loop of write() + time = OneWireEmulateTiming::READ_MIN[overdrive_mode]; + time = wait_while_gpio(time, true); + //while ((DIRECT_READ(pin_baseReg, pin_bitMask) == 0) && (--retries != 0)); + + return (time > 0); +} + +bool OneWireGpioSlave::receive(uint8_t* data, const uint8_t data_length) { + uint8_t bytes_received = 0; + for(; bytes_received < data_length; ++bytes_received) { + uint8_t value = 0; + + for(uint8_t bitMask = 0x01; bitMask != 0; bitMask <<= 1) { + if(receiveBit()) value |= bitMask; + } + + data[bytes_received] = value; + } + return (bytes_received != data_length); +} \ No newline at end of file diff --git a/applications/ibutton/one_wire_timings.h b/applications/ibutton/one_wire_timings.h new file mode 100644 index 00000000..49fc751e --- /dev/null +++ b/applications/ibutton/one_wire_timings.h @@ -0,0 +1,54 @@ +#pragma once +#include + +class __OneWireTiming { +public: + constexpr static const uint16_t TIMING_A = 6; + constexpr static const uint16_t TIMING_B = 64; + constexpr static const uint16_t TIMING_C = 60; + constexpr static const uint16_t TIMING_D = 10; + constexpr static const uint16_t TIMING_E = 9; + constexpr static const uint16_t TIMING_F = 55; + constexpr static const uint16_t TIMING_G = 0; + constexpr static const uint16_t TIMING_H = 480; + constexpr static const uint16_t TIMING_I = 70; + constexpr static const uint16_t TIMING_J = 410; +}; + +class OneWireTiming { +public: + constexpr static const uint16_t WRITE_1_DRIVE = __OneWireTiming::TIMING_A; + constexpr static const uint16_t WRITE_1_RELEASE = __OneWireTiming::TIMING_B; + + constexpr static const uint16_t WRITE_0_DRIVE = __OneWireTiming::TIMING_C; + constexpr static const uint16_t WRITE_0_RELEASE = __OneWireTiming::TIMING_D; + + constexpr static const uint16_t READ_DRIVE = __OneWireTiming::TIMING_A; + constexpr static const uint16_t READ_RELEASE = __OneWireTiming::TIMING_E; + constexpr static const uint16_t READ_DELAY_POST = __OneWireTiming::TIMING_F; + + constexpr static const uint16_t RESET_DELAY_PRE = __OneWireTiming::TIMING_G; + constexpr static const uint16_t RESET_DRIVE = __OneWireTiming::TIMING_H; + constexpr static const uint16_t RESET_RELEASE = __OneWireTiming::TIMING_I; + constexpr static const uint16_t RESET_DELAY_POST = __OneWireTiming::TIMING_J; +}; + +typedef uint32_t OneWiteTimeType; + +class OneWireEmulateTiming { +public: + constexpr static const OneWiteTimeType RESET_TIMEOUT = {5000}; + constexpr static const OneWiteTimeType RESET_MIN[2] = {430, 48}; + constexpr static const OneWiteTimeType RESET_MAX[2] = {960, 80}; + + constexpr static const OneWiteTimeType PRESENCE_TIMEOUT = {20}; + constexpr static const OneWiteTimeType PRESENCE_MIN[2] = {160, 8}; + constexpr static const OneWiteTimeType PRESENCE_MAX[2] = {480, 32}; + + constexpr static const OneWiteTimeType MSG_HIGH_TIMEOUT = {15000}; + constexpr static const OneWiteTimeType SLOT_MAX[2] = {135, 30}; + + constexpr static const OneWiteTimeType READ_MIN[2] = {20, 4}; + constexpr static const OneWiteTimeType READ_MAX[2] = {60, 10}; + constexpr static const OneWiteTimeType WRITE_ZERO[2] = {30, 8}; +}; \ No newline at end of file