OK it works 🎉

pull/480/head
Rick Carlino 2017-10-02 09:58:03 -05:00
parent 0f57b278c7
commit d9c44e367b
12 changed files with 56 additions and 48 deletions

View File

@ -1,7 +1,6 @@
import { Everything } from "../../interfaces";
export let bot: Everything["bot"] = {
"connectedToMQTT": true,
"stepSize": 100,
"controlPanelState": {
"homing_and_calibration": false,

View File

@ -11,7 +11,10 @@ export interface EdgeStatus {
/** Name of a connection between two points. "." can be read as "to".
* Example: "user.mqtt" => "User to MQTT". */
export type Edge = "user.mqtt" | "user.api";
export type Edge =
| "bot.mqtt"
| "user.mqtt"
| "user.api";
/** Mapping of known connection status.
* An `undefined` value means we don't know. */

View File

@ -3,6 +3,7 @@ import { Actions } from "../constants";
import { ConnectionState, EdgeStatus } from "./interfaces";
export const DEFAULT_STATE: ConnectionState = {
"bot.mqtt": undefined,
"user.mqtt": undefined,
"user.api": undefined,
};

View File

@ -388,7 +388,6 @@ export enum Actions {
// Devices
TOGGLE_CONTROL_PANEL_OPTION = "TOGGLE_CONTROL_PANEL_OPTION",
SET_MQTT_STATUS = "TOGGLE_MQTT",
CHANGE_STEP_SIZE = "CHANGE_STEP_SIZE",
SETTING_UPDATE_START = "SETTING_UPDATE_START",
SETTING_UPDATE_END = "SETTING_UPDATE_END",

View File

@ -2,7 +2,6 @@ import { versionOK, botReducer, initialState } from "../reducer";
import { Actions } from "../../constants";
import { ControlPanelState } from "../interfaces";
import { SyncStatus } from "farmbot/dist";
import { setMqttStatus } from "../actions";
describe("safeStringFetch", () => {
it("Checks the correct version on update", () => {
@ -103,11 +102,4 @@ describe("botRedcuer", () => {
.toBe(!initialState.encoder_visibility.scaled_encoders);
});
it("toggles MQTT status", () => {
let state = botReducer(initialState, setMqttStatus(false));
expect(state.connectedToMQTT).toBe(false);
state = botReducer(initialState, setMqttStatus(true));
expect(state.connectedToMQTT).toBe(true);
});
});

View File

@ -27,6 +27,7 @@ import { versionOK } from "./reducer";
import { oneOf, HttpData } from "../util";
import { Actions, Content } from "../constants";
import { mcuParamValidator } from "./update_interceptor";
import { dispatchNetworkUp, dispatchNetworkDown } from "../connectivity/index";
const ON = 1, OFF = 0;
export type ConfigKey = keyof McuParams;
@ -255,6 +256,11 @@ function readStatus() {
}
let NEED_VERSION_CHECK = true;
const bothUp = () => {
console.log("NO!");
dispatchNetworkUp("user.mqtt");
dispatchNetworkUp("bot.mqtt");
};
// Already filtering messages in FarmBot OS and the API- this is just for
// an additional layer of safety. If sensitive data ever hits a client, it will
// be reported to ROllbar for investigation.
@ -264,9 +270,9 @@ export function connectDevice(token: string): ConnectDeviceReturn {
return (dispatch: Function, getState: GetState) => {
const secure = location.protocol === "https:";
const bot = new Farmbot({ token, secure });
bot.on("online", () => dispatch(setMqttStatus(true)));
bot.on("online", () => dispatchNetworkUp("user.mqtt"));
bot.on("offline", () => {
dispatch(setMqttStatus(false));
dispatchNetworkDown("user.mqtt");
error(t(Content.MQTT_DISCONNECTED));
});
return bot
@ -281,7 +287,7 @@ export function connectDevice(token: string): ConnectDeviceReturn {
))
.catch(() => { });
bot.on("logs", function (msg: Log) {
dispatch(setMqttStatus(true));
bothUp();
if (isLog(msg) && !oneOf(BAD_WORDS, msg.message.toUpperCase())) {
maybeShowLog(msg);
dispatch(init({
@ -290,12 +296,22 @@ export function connectDevice(token: string): ConnectDeviceReturn {
uuid: "MUST_CHANGE",
body: msg
}));
// CORRECT SOLUTION: Give each device its own topic for publishing
// MQTT last will message.
// FAST SOLUTION: We would need to re-publish FBJS and FBOS to
// change topic structure. Instead, we will use
// inband signalling (for now).
// TODO: Make a `bot/device_123/offline` channel.
const died =
msg.message.includes("is offline") && msg.meta.type === "error";
console.log(msg.message);
died && dispatchNetworkDown("bot.mqtt");
} else {
throw new Error("Refusing to display log: " + JSON.stringify(msg));
}
});
bot.on("status", _.throttle(function (msg: BotStateTree) {
dispatch(setMqttStatus(true));
bothUp();
dispatch(incomingStatus(msg));
if (NEED_VERSION_CHECK) {
const IS_OK = versionOK(getState()
@ -311,7 +327,7 @@ export function connectDevice(token: string): ConnectDeviceReturn {
let alreadyToldYou = false;
bot.on("malformed", function () {
dispatch(setMqttStatus(true));
bothUp();
if (!alreadyToldYou) {
warning(t(Content.MALFORMED_MESSAGE_REC_UPGRADE));
alreadyToldYou = true;
@ -414,7 +430,3 @@ export function setSyncStatus(payload: SyncStatus) {
function badVersion() {
info(t("You are running an old version of FarmBot OS."), t("Please Update"), "red");
}
export let setMqttStatus = (payload: boolean) => ({
type: Actions.SET_MQTT_STATUS, payload
});

View File

@ -2,6 +2,8 @@ import {
browserToMQTT, botToMQTT, botToAPI, botToFirmware, browserToAPI
} from "../status_checks";
import * as moment from "moment";
import { ConnectionStatus } from "../../../connectivity/interfaces";
import { betterMerge } from "../../../util";
describe("botToAPI()", () => {
it("handles connectivity", () => {
@ -24,8 +26,16 @@ describe("botToAPI()", () => {
});
describe("botToMQTT()", () => {
function stat(input: Partial<ConnectionStatus> = {}): ConnectionStatus {
return betterMerge({
from: "bot",
to: "mqtt",
at: "2017-09-27T07:52:37.003-05:00",
stat: "up"
}, input as ConnectionStatus);
}
it("handles connectivity", () => {
const result = botToMQTT("\"2017-09-27T07:52:37.003-05:00\"");
const result = botToMQTT(stat());
expect(result.connectionStatus).toBeTruthy();
expect(result.children).toContain("Connected ");
expect(result.children).toContain(" ago");

View File

@ -27,23 +27,16 @@ export function botToAPI(lastSeen: moment.Moment | undefined,
return status;
}
export function botToMQTT(lastSeen: string | undefined,
now = moment()): StatusRowProps {
const output: StatusRowProps = {
const NOT_SEEN = "We are not seeing any realtime messages from the bot right now.";
export function botToMQTT(stat: ConnectionStatus | undefined): StatusRowProps {
return {
connectionName: "botMQTT",
from: "Bot",
to: "Message Broker",
connectionStatus: false,
children: "We are not seeing any realtime messages from the bot right now."
connectionStatus: !!(stat && stat.state === "up"),
children: stat ?
`Last message seen ${moment(new Date(stat.at)).fromNow()}.` : NOT_SEEN
};
if (lastSeen) {
output.connectionStatus = true;
const ago = moment(new Date(JSON.parse(lastSeen))).fromNow();
output.children = `Connected ${ago}.`;
}
return output;
}
export function browserToMQTT(online?: boolean): StatusRowProps {

View File

@ -20,16 +20,17 @@ export class Devices extends React.Component<Props, {}> {
/** A record of all the things we know about connectivity right now. */
get flags(): Record<DiagnosisName, StatusRowProps> {
const mqttConnected = this.props.bot.connectedToMQTT;
const lastSeen = this.props.deviceAccount.body.last_seen;
const timestamp = this.props.bot.hardware.user_env["LAST_CLIENT_CONNECTED"];
const mqttConnected =
this.props.userToMqtt && this.props.userToMqtt.state === "up";
const botApiTimestamp = this.props.deviceAccount.body.last_seen;
const fwVersion = this.props.bot.hardware
.informational_settings.firmware_version;
return {
userMQTT: browserToMQTT(mqttConnected),
userAPI: browserToAPI(this.props.connectivity),
botMQTT: botToMQTT(timestamp),
botAPI: botToAPI(lastSeen ? moment(lastSeen) : undefined, moment()),
userAPI: browserToAPI(this.props.userToApi),
botMQTT: botToMQTT(this.props.botToMqtt),
botAPI: botToAPI(botApiTimestamp ? moment(botApiTimestamp) : undefined, moment()),
botFirmware: botToFirmware(fwVersion),
};
}

View File

@ -20,7 +20,9 @@ import { EncoderDisplay } from "../controls/interfaces";
import { ConnectionStatus } from "../connectivity/interfaces";
export interface Props {
connectivity: ConnectionStatus | undefined;
userToApi: ConnectionStatus | undefined;
userToMqtt: ConnectionStatus | undefined;
botToMqtt: ConnectionStatus | undefined;
auth: AuthState | undefined;
bot: BotState;
deviceAccount: TaggedDevice;
@ -39,7 +41,6 @@ export interface DeviceAccountSettings {
}
export interface BotState {
connectedToMQTT: boolean;
/** How many steps to move when the user presses a manual movement arrow */
stepSize: number;
/** The current os version on the github release api */

View File

@ -26,7 +26,6 @@ export function versionOK(stringyVersion = "0.0.0",
}
}
export let initialState: BotState = {
connectedToMQTT: false,
stepSize: 100,
controlPanelState: {
homing_and_calibration: false,
@ -136,8 +135,4 @@ export let botReducer = generateReducer<BotState>(initialState)
.add<EncoderDisplay>(Actions.DISPLAY_ENCODER_DATA, (s, { payload }) => {
s.encoder_visibility[payload] = !s.encoder_visibility[payload];
return s;
})
.add<boolean>(Actions.SET_MQTT_STATUS, (s, a) => {
s.connectedToMQTT = a.payload;
return s;
});

View File

@ -7,7 +7,9 @@ import {
export function mapStateToProps(props: Everything): Props {
return {
connectivity: props.connectivity["user.api"],
userToApi: props.connectivity["user.api"],
userToMqtt: props.connectivity["user.mqtt"],
botToMqtt: props.connectivity["bot.mqtt"],
deviceAccount: getDeviceAccountSettings(props.resources.index),
auth: props.auth,
bot: props.bot,