[FL-2131] IR: continuous signal tx on learn scene (#1002)
Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									3922ae93b7
								
							
						
					
					
						commit
						ddd909faa0
					
				| @ -46,6 +46,8 @@ static void dialogs_app_message_callback(DialogExResult result, void* context) { | |||||||
|     case DialogExResultCenter: |     case DialogExResultCenter: | ||||||
|         message_context->result = DialogMessageButtonCenter; |         message_context->result = DialogMessageButtonCenter; | ||||||
|         break; |         break; | ||||||
|  |     default: | ||||||
|  |         break; | ||||||
|     } |     } | ||||||
|     API_LOCK_UNLOCK(message_context->lock); |     API_LOCK_UNLOCK(message_context->lock); | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ struct DialogEx { | |||||||
|     View* view; |     View* view; | ||||||
|     void* context; |     void* context; | ||||||
|     DialogExResultCallback callback; |     DialogExResultCallback callback; | ||||||
|  |     bool enable_extended_events; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -96,17 +97,44 @@ static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { | |||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     // Process key presses only
 |     if(dialog_ex->callback) { | ||||||
|     if(event->type == InputTypeShort && dialog_ex->callback) { |         if(event->type == InputTypeShort) { | ||||||
|         if(event->key == InputKeyLeft && left_text != NULL) { |             if(event->key == InputKeyLeft && left_text != NULL) { | ||||||
|             dialog_ex->callback(DialogExResultLeft, dialog_ex->context); |                 dialog_ex->callback(DialogExResultLeft, dialog_ex->context); | ||||||
|             consumed = true; |                 consumed = true; | ||||||
|         } else if(event->key == InputKeyOk && center_text != NULL) { |             } else if(event->key == InputKeyOk && center_text != NULL) { | ||||||
|             dialog_ex->callback(DialogExResultCenter, dialog_ex->context); |                 dialog_ex->callback(DialogExResultCenter, dialog_ex->context); | ||||||
|             consumed = true; |                 consumed = true; | ||||||
|         } else if(event->key == InputKeyRight && right_text != NULL) { |             } else if(event->key == InputKeyRight && right_text != NULL) { | ||||||
|             dialog_ex->callback(DialogExResultRight, dialog_ex->context); |                 dialog_ex->callback(DialogExResultRight, dialog_ex->context); | ||||||
|             consumed = true; |                 consumed = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(event->type == InputTypePress && dialog_ex->enable_extended_events) { | ||||||
|  |             if(event->key == InputKeyLeft && left_text != NULL) { | ||||||
|  |                 dialog_ex->callback(DialogExPressLeft, dialog_ex->context); | ||||||
|  |                 consumed = true; | ||||||
|  |             } else if(event->key == InputKeyOk && center_text != NULL) { | ||||||
|  |                 dialog_ex->callback(DialogExPressCenter, dialog_ex->context); | ||||||
|  |                 consumed = true; | ||||||
|  |             } else if(event->key == InputKeyRight && right_text != NULL) { | ||||||
|  |                 dialog_ex->callback(DialogExPressRight, dialog_ex->context); | ||||||
|  |                 consumed = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(event->type == InputTypeRelease && dialog_ex->enable_extended_events) { | ||||||
|  |             if(event->key == InputKeyLeft && left_text != NULL) { | ||||||
|  |                 dialog_ex->callback(DialogExReleaseLeft, dialog_ex->context); | ||||||
|  |                 consumed = true; | ||||||
|  |             } else if(event->key == InputKeyOk && center_text != NULL) { | ||||||
|  |                 dialog_ex->callback(DialogExReleaseCenter, dialog_ex->context); | ||||||
|  |                 consumed = true; | ||||||
|  |             } else if(event->key == InputKeyRight && right_text != NULL) { | ||||||
|  |                 dialog_ex->callback(DialogExReleaseRight, dialog_ex->context); | ||||||
|  |                 consumed = true; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -144,6 +172,7 @@ DialogEx* dialog_ex_alloc() { | |||||||
| 
 | 
 | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|  |     dialog_ex->enable_extended_events = false; | ||||||
|     return dialog_ex; |     return dialog_ex; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -262,3 +291,13 @@ void dialog_ex_reset(DialogEx* dialog_ex) { | |||||||
|     dialog_ex->context = NULL; |     dialog_ex->context = NULL; | ||||||
|     dialog_ex->callback = NULL; |     dialog_ex->callback = NULL; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void dialog_ex_enable_extended_events(DialogEx* dialog_ex) { | ||||||
|  |     furi_assert(dialog_ex); | ||||||
|  |     dialog_ex->enable_extended_events = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dialog_ex_disable_extended_events(DialogEx* dialog_ex) { | ||||||
|  |     furi_assert(dialog_ex); | ||||||
|  |     dialog_ex->enable_extended_events = false; | ||||||
|  | } | ||||||
|  | |||||||
| @ -19,6 +19,12 @@ typedef enum { | |||||||
|     DialogExResultLeft, |     DialogExResultLeft, | ||||||
|     DialogExResultCenter, |     DialogExResultCenter, | ||||||
|     DialogExResultRight, |     DialogExResultRight, | ||||||
|  |     DialogExPressLeft, | ||||||
|  |     DialogExPressCenter, | ||||||
|  |     DialogExPressRight, | ||||||
|  |     DialogExReleaseLeft, | ||||||
|  |     DialogExReleaseCenter, | ||||||
|  |     DialogExReleaseRight, | ||||||
| } DialogExResult; | } DialogExResult; | ||||||
| 
 | 
 | ||||||
| /** DialogEx result callback type
 | /** DialogEx result callback type
 | ||||||
| @ -145,6 +151,18 @@ void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text); | |||||||
|  */ |  */ | ||||||
| void dialog_ex_reset(DialogEx* dialog_ex); | void dialog_ex_reset(DialogEx* dialog_ex); | ||||||
| 
 | 
 | ||||||
|  | /** Enable press/release events
 | ||||||
|  |  * | ||||||
|  |  * @param      dialog_ex  DialogEx instance | ||||||
|  |  */ | ||||||
|  | void dialog_ex_enable_extended_events(DialogEx* dialog_ex); | ||||||
|  | 
 | ||||||
|  | /** Disable press/release events
 | ||||||
|  |  * | ||||||
|  |  * @param      dialog_ex  DialogEx instance | ||||||
|  |  */ | ||||||
|  | void dialog_ex_disable_extended_events(DialogEx* dialog_ex); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -279,3 +279,8 @@ const IrdaAppSignal& IrdaApp::get_received_signal() const { | |||||||
| void IrdaApp::set_received_signal(const IrdaAppSignal& signal) { | void IrdaApp::set_received_signal(const IrdaAppSignal& signal) { | ||||||
|     received_signal = signal; |     received_signal = signal; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void IrdaApp::signal_sent_callback(void* context) { | ||||||
|  |     IrdaApp* app = static_cast<IrdaApp*>(context); | ||||||
|  |     app->notify_blink_green(); | ||||||
|  | } | ||||||
|  | |||||||
| @ -87,6 +87,7 @@ public: | |||||||
| 
 | 
 | ||||||
|     static void text_input_callback(void* context); |     static void text_input_callback(void* context); | ||||||
|     static void popup_callback(void* context); |     static void popup_callback(void* context); | ||||||
|  |     static void signal_sent_callback(void* context); | ||||||
| 
 | 
 | ||||||
|     IrdaApp(); |     IrdaApp(); | ||||||
|     ~IrdaApp(); |     ~IrdaApp(); | ||||||
|  | |||||||
| @ -1,8 +1,10 @@ | |||||||
| #include "furi.h" | #include <furi.h> | ||||||
| #include "gui/modules/button_panel.h" | #include <gui/modules/button_panel.h> | ||||||
|  | #include <gui/modules/dialog_ex.h> | ||||||
|  | #include <callback-connector.h> | ||||||
|  | 
 | ||||||
| #include "irda_app.h" | #include "irda_app.h" | ||||||
| #include "irda/irda_app_event.h" | #include "irda/irda_app_event.h" | ||||||
| #include <callback-connector.h> |  | ||||||
| 
 | 
 | ||||||
| IrdaAppViewManager::IrdaAppViewManager() { | IrdaAppViewManager::IrdaAppViewManager() { | ||||||
|     event_queue = osMessageQueueNew(10, sizeof(IrdaAppEvent), NULL); |     event_queue = osMessageQueueNew(10, sizeof(IrdaAppEvent), NULL); | ||||||
| @ -113,11 +115,16 @@ void IrdaAppViewManager::receive_event(IrdaAppEvent* event) { | |||||||
| 
 | 
 | ||||||
| void IrdaAppViewManager::send_event(IrdaAppEvent* event) { | void IrdaAppViewManager::send_event(IrdaAppEvent* event) { | ||||||
|     uint32_t timeout = 0; |     uint32_t timeout = 0; | ||||||
|     /* Rapid button hammering on Remote Scene causes queue overflow - ignore it,
 |     /* Rapid button hammering on signal send scenes causes queue overflow - ignore it,
 | ||||||
|      * but try to keep button release event - it switches off IRDA DMA sending. */ |      * but try to keep button release event - it switches off IRDA DMA sending. */ | ||||||
|     if(event->type == IrdaAppEvent::Type::MenuSelectedRelease) { |     if(event->type == IrdaAppEvent::Type::MenuSelectedRelease) { | ||||||
|         timeout = 200; |         timeout = 200; | ||||||
|     } |     } | ||||||
|  |     if((event->type == IrdaAppEvent::Type::DialogExSelected) && | ||||||
|  |        (event->payload.dialog_ex_result == DialogExReleaseCenter)) { | ||||||
|  |         timeout = 200; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     osMessageQueuePut(event_queue, event, 0, timeout); |     osMessageQueuePut(event_queue, event, 0, timeout); | ||||||
|     /* furi_check(result == osOK); */ |     /* furi_check(result == osOK); */ | ||||||
| } | } | ||||||
|  | |||||||
| @ -50,6 +50,7 @@ public: | |||||||
|     void on_enter(IrdaApp* app) final; |     void on_enter(IrdaApp* app) final; | ||||||
|     bool on_event(IrdaApp* app, IrdaAppEvent* event) final; |     bool on_event(IrdaApp* app, IrdaAppEvent* event) final; | ||||||
|     void on_exit(IrdaApp* app) final; |     void on_exit(IrdaApp* app) final; | ||||||
|  |     bool button_pressed = false; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class IrdaAppSceneLearnEnterName : public IrdaAppScene { | class IrdaAppSceneLearnEnterName : public IrdaAppScene { | ||||||
|  | |||||||
| @ -57,6 +57,8 @@ bool IrdaAppSceneAskBack::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|             app->switch_to_previous_scene(); |             app->switch_to_previous_scene(); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|             break; |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|         case DialogExResultCenter: |         case DialogExResultCenter: | ||||||
|             furi_assert(0); |             furi_assert(0); | ||||||
|             break; |             break; | ||||||
|         case DialogExResultRight: |         case DialogExResultRight: { | ||||||
|             auto remote_manager = app->get_remote_manager(); |             auto remote_manager = app->get_remote_manager(); | ||||||
|             bool result = false; |             bool result = false; | ||||||
|             if(app->get_edit_element() == IrdaApp::EditElement::Remote) { |             if(app->get_edit_element() == IrdaApp::EditElement::Remote) { | ||||||
| @ -88,6 +88,9 @@ bool IrdaAppSceneEditDelete::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|             } |             } | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| #include "../irda_app.h" | #include <gui/modules/dialog_ex.h> | ||||||
| #include <file_worker_cpp.h> | #include <file_worker_cpp.h> | ||||||
| #include "irda.h" |  | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <dolphin/dolphin.h> | #include <dolphin/dolphin.h> | ||||||
| 
 | 
 | ||||||
|  | #include "../irda_app.h" | ||||||
|  | #include "irda.h" | ||||||
|  | 
 | ||||||
| static void dialog_result_callback(DialogExResult result, void* context) { | static void dialog_result_callback(DialogExResult result, void* context) { | ||||||
|     auto app = static_cast<IrdaApp*>(context); |     auto app = static_cast<IrdaApp*>(context); | ||||||
|     IrdaAppEvent event; |     IrdaAppEvent event; | ||||||
| @ -21,6 +23,11 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { | |||||||
|     DOLPHIN_DEED(DolphinDeedIrLearnSuccess); |     DOLPHIN_DEED(DolphinDeedIrLearnSuccess); | ||||||
|     app->notify_green_on(); |     app->notify_green_on(); | ||||||
| 
 | 
 | ||||||
|  |     irda_worker_tx_set_get_signal_callback( | ||||||
|  |         app->get_irda_worker(), irda_worker_tx_get_signal_steady_callback, app); | ||||||
|  |     irda_worker_tx_set_signal_sent_callback( | ||||||
|  |         app->get_irda_worker(), IrdaApp::signal_sent_callback, app); | ||||||
|  | 
 | ||||||
|     auto signal = app->get_received_signal(); |     auto signal = app->get_received_signal(); | ||||||
| 
 | 
 | ||||||
|     if(!signal.is_raw()) { |     if(!signal.is_raw()) { | ||||||
| @ -55,6 +62,7 @@ void IrdaAppSceneLearnSuccess::on_enter(IrdaApp* app) { | |||||||
|     dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63); |     dialog_ex_set_icon(dialog_ex, 0, 1, &I_DolphinReadingSuccess_59x63); | ||||||
|     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); |     dialog_ex_set_result_callback(dialog_ex, dialog_result_callback); | ||||||
|     dialog_ex_set_context(dialog_ex, app); |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_enable_extended_events(dialog_ex); | ||||||
| 
 | 
 | ||||||
|     view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); |     view_manager->switch_to(IrdaAppViewManager::ViewType::DialogEx); | ||||||
| } | } | ||||||
| @ -63,36 +71,65 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
|     if(event->type == IrdaAppEvent::Type::Tick) { |     if(event->type == IrdaAppEvent::Type::Tick) { | ||||||
|         /* Send event every tick to suppress any switching off green light */ |         /* Send event every tick to suppress any switching off green light */ | ||||||
|         app->notify_green_on(); |         if(!button_pressed) { | ||||||
|  |             app->notify_green_on(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(event->type == IrdaAppEvent::Type::DialogExSelected) { |     if(event->type == IrdaAppEvent::Type::DialogExSelected) { | ||||||
|         switch(event->payload.dialog_ex_result) { |         switch(event->payload.dialog_ex_result) { | ||||||
|         case DialogExResultLeft: |         case DialogExResultLeft: | ||||||
|             app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); |             consumed = true; | ||||||
|  |             if(!button_pressed) { | ||||||
|  |                 app->switch_to_next_scene_without_saving(IrdaApp::Scene::Learn); | ||||||
|  |             } | ||||||
|             break; |             break; | ||||||
|         case DialogExResultCenter: { |  | ||||||
|             app->notify_sent_just_learnt(); |  | ||||||
|             auto signal = app->get_received_signal(); |  | ||||||
|             signal.transmit(); |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         case DialogExResultRight: { |         case DialogExResultRight: { | ||||||
|  |             consumed = true; | ||||||
|             FileWorkerCpp file_worker; |             FileWorkerCpp file_worker; | ||||||
|             if(file_worker.check_errors()) { |             if(!button_pressed) { | ||||||
|                 app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); |                 if(file_worker.check_errors()) { | ||||||
|             } else { |                     app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); | ||||||
|                 app->switch_to_previous_scene(); |                 } else { | ||||||
|  |                     app->switch_to_previous_scene(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |         case DialogExPressCenter: | ||||||
|  |             if(!button_pressed) { | ||||||
|  |                 button_pressed = true; | ||||||
|  |                 app->notify_click_and_green_blink(); | ||||||
|  | 
 | ||||||
|  |                 auto signal = app->get_received_signal(); | ||||||
|  |                 if(signal.is_raw()) { | ||||||
|  |                     irda_worker_set_raw_signal( | ||||||
|  |                         app->get_irda_worker(), | ||||||
|  |                         signal.get_raw_signal().timings, | ||||||
|  |                         signal.get_raw_signal().timings_cnt); | ||||||
|  |                 } else { | ||||||
|  |                     irda_worker_set_decoded_signal(app->get_irda_worker(), &signal.get_message()); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 irda_worker_tx_start(app->get_irda_worker()); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |         case DialogExReleaseCenter: | ||||||
|  |             if(button_pressed) { | ||||||
|  |                 button_pressed = false; | ||||||
|  |                 irda_worker_tx_stop(app->get_irda_worker()); | ||||||
|  |                 app->notify_green_off(); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(event->type == IrdaAppEvent::Type::Back) { |     if(event->type == IrdaAppEvent::Type::Back) { | ||||||
|         app->switch_to_next_scene(IrdaApp::Scene::AskBack); |         if(!button_pressed) { | ||||||
|  |             app->switch_to_next_scene(IrdaApp::Scene::AskBack); | ||||||
|  |         } | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -104,4 +141,6 @@ void IrdaAppSceneLearnSuccess::on_exit(IrdaApp* app) { | |||||||
|     DialogEx* dialog_ex = view_manager->get_dialog_ex(); |     DialogEx* dialog_ex = view_manager->get_dialog_ex(); | ||||||
|     dialog_ex_reset(dialog_ex); |     dialog_ex_reset(dialog_ex); | ||||||
|     app->notify_green_off(); |     app->notify_green_off(); | ||||||
|  |     irda_worker_tx_set_get_signal_callback(app->get_irda_worker(), nullptr, nullptr); | ||||||
|  |     irda_worker_tx_set_signal_sent_callback(app->get_irda_worker(), nullptr, nullptr); | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,11 +29,6 @@ static void button_menu_callback(void* context, int32_t index, InputType type) { | |||||||
|     app->get_view_manager()->send_event(&event); |     app->get_view_manager()->send_event(&event); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void irda_app_message_sent_callback(void* context) { |  | ||||||
|     IrdaApp* app = static_cast<IrdaApp*>(context); |  | ||||||
|     app->notify_blink_green(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void IrdaAppSceneRemote::on_enter(IrdaApp* app) { | void IrdaAppSceneRemote::on_enter(IrdaApp* app) { | ||||||
|     IrdaAppViewManager* view_manager = app->get_view_manager(); |     IrdaAppViewManager* view_manager = app->get_view_manager(); | ||||||
|     ButtonMenu* button_menu = view_manager->get_button_menu(); |     ButtonMenu* button_menu = view_manager->get_button_menu(); | ||||||
| @ -44,7 +39,7 @@ void IrdaAppSceneRemote::on_enter(IrdaApp* app) { | |||||||
|     irda_worker_tx_set_get_signal_callback( |     irda_worker_tx_set_get_signal_callback( | ||||||
|         app->get_irda_worker(), irda_worker_tx_get_signal_steady_callback, app); |         app->get_irda_worker(), irda_worker_tx_get_signal_steady_callback, app); | ||||||
|     irda_worker_tx_set_signal_sent_callback( |     irda_worker_tx_set_signal_sent_callback( | ||||||
|         app->get_irda_worker(), irda_app_message_sent_callback, app); |         app->get_irda_worker(), IrdaApp::signal_sent_callback, app); | ||||||
|     buttons_names = remote_manager->get_button_list(); |     buttons_names = remote_manager->get_button_list(); | ||||||
| 
 | 
 | ||||||
|     i = 0; |     i = 0; | ||||||
|  | |||||||
| @ -320,6 +320,7 @@ void irda_worker_rx_enable_blink_on_receiving(IrdaWorker* instance, bool enable) | |||||||
| void irda_worker_tx_start(IrdaWorker* instance) { | void irda_worker_tx_start(IrdaWorker* instance) { | ||||||
|     furi_assert(instance); |     furi_assert(instance); | ||||||
|     furi_assert(instance->state == IrdaWorkerStateIdle); |     furi_assert(instance->state == IrdaWorkerStateIdle); | ||||||
|  |     furi_assert(instance->tx.get_signal_callback); | ||||||
| 
 | 
 | ||||||
|     // size have to be greater than api hal irda async tx buffer size
 |     // size have to be greater than api hal irda async tx buffer size
 | ||||||
|     xStreamBufferSetTriggerLevel(instance->stream, sizeof(IrdaWorkerTiming)); |     xStreamBufferSetTriggerLevel(instance->stream, sizeof(IrdaWorkerTiming)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Albert Kharisov
						Albert Kharisov