FarmBot/Farmbot-Web-App

View on GitHub
frontend/devices/reducer.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {
  BotState,
  HardwareState,
  MinOsFeatureLookup,
  OsUpdateInfo,
} from "./interfaces";
import { generateReducer } from "../redux/generate_reducer";
import { Actions } from "../constants";
import { maybeNegateStatus } from "../connectivity/maybe_negate_status";
import { ReduxAction } from "../redux/interfaces";
import {
  connectivityReducer, PingResultPayload, recentPingOk,
} from "../connectivity/reducer";
import { versionOK } from "../util";
import { updateMotorHistoryArray } from "../controls/move/motor_position_plot";

const afterEach = (state: BotState, a: ReduxAction<{}>) => {
  state.connectivity = connectivityReducer(state.connectivity, a);
  return state;
};

export const initialState = (): BotState => ({
  consistent: true,
  stepSize: 100,
  hardware: {
    gpio_registry: {},
    mcu_params: {},
    jobs: {},
    location_data: {
      "position": {
        x: undefined,
        y: undefined,
        z: undefined
      },
      "scaled_encoders": {
        x: undefined,
        y: undefined,
        z: undefined
      },
      "raw_encoders": {
        x: undefined,
        y: undefined,
        z: undefined
      },
    },
    pins: {},
    configuration: {},
    informational_settings: {
      busy: false,
      locked: false,
      commit: "---",
      target: "---",
      env: "---",
      node_name: "---",
      firmware_version: "---",
      firmware_commit: "---",
    },
    user_env: {},
    process_info: {
      farmwares: {},
    }
  },
  dirty: false,
  osUpdateVersion: undefined,
  minOsFeatureData: undefined,
  osReleaseNotes: undefined,
  connectivity: {
    uptime: {
      "bot.mqtt": undefined,
      "user.mqtt": undefined,
      "user.api": undefined
    },
    pings: {}
  },
  needVersionCheck: true,
  alreadyToldUserAboutMalformedMsg: false,
});

export const botReducer = generateReducer<BotState>(initialState())
  .afterEach(afterEach)
  .add<boolean>(Actions.SET_CONSISTENCY, (s, a) => {
    s.consistent = a.payload;
    s.hardware.informational_settings.sync_status = maybeNegateStatus({
      consistent: s.consistent,
      syncStatus: s.hardware.informational_settings.sync_status,
      fbosVersion: s.hardware.informational_settings.controller_version,
    });
    return s;
  })
  .add<void>(Actions.SETTING_UPDATE_START, (s) => {
    s.isUpdating = true;
    return s;
  })
  .add<void>(Actions.SETTING_UPDATE_END, (s) => {
    s.isUpdating = false;
    return s;
  })
  .add<number>(Actions.CHANGE_STEP_SIZE, (s, a) => {
    return Object.assign({}, s, {
      stepSize: a.payload
    });
  })
  .add<OsUpdateInfo>(Actions.FETCH_OS_UPDATE_INFO_OK, (s, { payload }) => {
    s.osUpdateVersion = payload.version;
    return s;
  })
  .add<MinOsFeatureLookup>(Actions.FETCH_MIN_OS_FEATURE_INFO_OK,
    (s, { payload }) => {
      s.minOsFeatureData = payload;
      return s;
    })
  .add<string>(Actions.FETCH_OS_RELEASE_NOTES_OK,
    (s, { payload }) => {
      s.osReleaseNotes = payload;
      return s;
    })
  .add<HardwareState>(Actions.STATUS_UPDATE, statusHandler)
  .add<void>(Actions.STASH_STATUS, (s) => {
    stash(s);
    return s;
  })
  .add<boolean>(Actions.SET_NEEDS_VERSION_CHECK, (s, { payload }) => {
    s.needVersionCheck = payload;
    return s;
  })
  .add<boolean>(Actions.SET_MALFORMED_NOTIFICATION_SENT, (s, { payload }) => {
    s.alreadyToldUserAboutMalformedMsg = payload;
    return s;
  })
  .add<void>(Actions._RESOURCE_NO, (s) => {
    unstash(s);
    return s;
  })
  .add<PingResultPayload>(Actions.PING_OK, (s) => {
    // Going from "down" to "up"
    const currentState = s.connectivity.uptime["bot.mqtt"];
    const backOnline = currentState && currentState.state === "down";
    backOnline && unstash(s);
    return s;
  })
  .add<PingResultPayload>(Actions.PING_NO, (s, { payload }) => {
    if (!recentPingOk(s.connectivity.pings, payload.id)) {
      stash(s);
      s.hardware.informational_settings.sync_status = undefined;
    }
    return s;
  });

/** Mutate syncStatus when transitioning from consistent to inconsistent. */
const stash = (s: BotState) => {
  const botStatus = s.hardware.informational_settings.sync_status;
  botStatus && (s.statusStash = botStatus);
};

/** Put the old syncStatus back where it was after bot becomes consistent. */
const unstash = (s: BotState) =>
  s.hardware.informational_settings.sync_status = s.statusStash;

function statusHandler(state: BotState,
  action: ReduxAction<HardwareState>): BotState {
  const { payload } = action;
  state.hardware = payload;

  updateMotorHistoryArray(payload.location_data);

  const { informational_settings } = state.hardware;
  const syncStatus = informational_settings.sync_status;
  /** USE CASE: You reboot the bot. The old state values are still hanging
   * around. You think the bot is broke, but it isn't. The FE is holding on
   * to stale data. */
  if (syncStatus === "maintenance") {
    const emptyState = initialState();
    state.hardware = emptyState.hardware;
    state.hardware.informational_settings.sync_status = "maintenance";
    return state;
  }

  const info = {
    consistent: state.consistent,
    syncStatus,
    fbosVersion: informational_settings.controller_version,
  };
  state.consistent = info.consistent;
  info.consistent = state.consistent;

  const nextSyncStatus = maybeNegateStatus(info);

  versionOK(informational_settings.controller_version);
  state.hardware.informational_settings.sync_status = nextSyncStatus;
  return state;
}