packages feed

keera-posture-0.2.2: src/Controller/Conditions/Detector.hs

-- | Manages the detector, starts/stops it as requested.
module Controller.Conditions.Detector where

-- External imports
import           AI.CV.ImageProcessors
import           Control.Concurrent
import qualified Control.Exception       as E
import           Control.Exception.Extra
import           Control.Monad
import           Control.Monad.IfElse
import           Data.Maybe

-- Internal imports
import CombinedEnvironment
import Model.Model (Status(..))
import Hails.MVC.Model.ProtectedModel.Reactive
import AI.CV.PostureProcessors

-- | Try to (re-)start the detector when requested
installHandlers :: CEnv -> IO()
installHandlers cenv = do
 let pm = model cenv
 onEvents pm [ Initialised, NotificationEnabledChanged ] $ startDetector cenv
 onEvent pm CameraChanged $ restartDetector cenv

-- | Runs the detector with the configured parameters until it is disabled
startDetector :: CEnv -> IO ()
startDetector cenv = void $ forkOS $ whenM (detectorCanStart cenv) $ do
  let pm = model cenv

  -- Mark that the detector is running in the model
  setDetector pm True

  cal <- getter calibrationParamsField pm
  del <- getter notificationDelayField pm
  cor <- getter correctionFactorField  pm
  cam <- getter cameraField            pm

  when (isJust cal) $ E.handle (anyway $ wrongCam pm) $ do
      state <- createInitialState
      let params = DetectionParams (fromJust cal) cor del
      detectorProc <- initialiseDetector cam params state
      setter cameraStatusField pm (Just True)
      void $ detectorProc `runTill` updateStatus cenv params

  -- Mark the detector as not running in the model
  setDetector pm False

-- | Restart the detector (disable it, wait for the thread to die, and start it
-- again).
restartDetector :: CEnv -> IO()
restartDetector cenv = void $ forkIO $ do
  let pm = model cenv

  -- Remember if the system was enabled and restore the same state later.
  enabled <- getter notificationEnabledField pm
  setter notificationEnabledField pm False

  -- This value is completely random. It simply works, but there must be a
  -- better way.
  threadDelay 1000000

  -- What for the detector to die
  waitForDetector pm

  -- Reset the notification status
  setter notificationEnabledField pm enabled

  -- Start the system again
  startDetector cenv

-- | Determine whether the detector can start
detectorCanStart :: CEnv -> IO Bool
detectorCanStart cenv = do
  let pm = model cenv
  -- Basic conf from file is ok
  confOk  <- fmap isJust $ getter calibrationParamsField (model cenv)
  -- Detection is enabled
  st      <- getter statusField pm
  notif   <- getter notificationEnabledField pm
  let detectionEnabled = notif && st < StatusCallibrating
  -- No detector is running
  detectorNotRunning <- fmap not $ getDetector pm
  return (confOk && detectionEnabled && detectorNotRunning)

-- | Updates the status in the model based on the detector's state and last
-- detection parameters
updateStatus :: CEnv -> DetectionParams -> InternalState -> IO Bool
updateStatus cenv params state = do

  -- Guarded rule: when detection is enabled, the posture status
  -- will be reflected in the application
  --
  -- let callibrating     = (< StatusCallibrating) <$> statusField
  --     detectionEnabled = (&&) <$> notifiactionEnabledField
  --                             <*> callibrating
  -- ifM detectionEnabled
  --    (detectionStatusField params state) =:> liftW newStatus statusField

  let pm = model cenv
  notif <- getter notificationEnabledField pm
  st    <- getter statusField pm
  let detectionEnabled = notif && st < StatusCallibrating

  -- Update the status when detection is enabled
  when detectionEnabled $ setter statusField pm (newStatus (detectionStatus params state))

  return $ not detectionEnabled

-- | Computes the new global state depending on the detection results
newStatus :: DetectionStatus -> Status
newStatus DetectionOk      = StatusIdle
newStatus DetectionWrong   = StatusNotifying
newStatus DetectionUnknown = StatusFinding