auth and change kmn

This commit is contained in:
Svante Kaiser 2024-01-10 19:04:40 +03:00
parent 48edd149de
commit 86d7343f64
33 changed files with 2795 additions and 2500 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
build build
node_modules node_modules
data/www

Binary file not shown.

Binary file not shown.

View File

@ -56,11 +56,11 @@ const IntercomSettingsForm: FC = () => {
</ValidatedTextField> </ValidatedTextField>
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="firstAppartment" name="firstApartment"
label="First Appartment" label="First Appartment"
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.firstAppartment} value={data.firstApartment}
onChange={updateFormValue} onChange={updateFormValue}
margin="normal" margin="normal"
/> />

View File

@ -1,4 +1,4 @@
import { FC } from "react"; import { FC, useState } from "react";
import { Avatar, Box, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from "@mui/material"; import { Avatar, Box, Button, Divider, List, ListItem, ListItemAvatar, ListItemText, Theme, useTheme } from "@mui/material";
import PowerIcon from '@mui/icons-material/Power'; import PowerIcon from '@mui/icons-material/Power';
@ -9,12 +9,12 @@ import MeetingRoomIcon from '@mui/icons-material/MeetingRoom';
import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
import NotificationsIcon from '@mui/icons-material/Notifications'; import NotificationsIcon from '@mui/icons-material/Notifications';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { useSnackbar } from "notistack";
import * as IntercomApi from "../../api/intercom"; import * as IntercomApi from "../../api/intercom";
import { DoorStatus, IntercomConnectionStatus, IntercomStatus, SwitchDoorType } from "../../types"; import { DoorStatus, IntercomConnectionStatus, IntercomStatus, SwitchDoorType } from "../../types";
import { ButtonRow, FormLoader, SectionContent } from "../../components"; import { ButtonRow, FormLoader, SectionContent } from "../../components";
import { useRest } from "../../utils"; import { extractErrorMessage, useRest } from "../../utils";
import {useSnackbar} from "notistack";
export const intercomStatusHighlight = ({ status }: IntercomStatus, theme: Theme) => { export const intercomStatusHighlight = ({ status }: IntercomStatus, theme: Theme) => {
switch (status) { switch (status) {
@ -77,26 +77,35 @@ export const doorStatus = (status: DoorStatus) => {
} }
}; };
export const openDoor = () => { export const openDoorApi = async () => {
IntercomApi.switchDoor({ await IntercomApi.switchDoor({
type: SwitchDoorType.JOGGING, type: SwitchDoorType.JOGGING,
status: DoorStatus.OPENED, status: DoorStatus.OPENED,
time: 1 time: 1
}) })
.then(() => {
const { enqueueSnackbar } = useSnackbar();
enqueueSnackbar("Update successful", { variant: 'success' });
})
.catch((error) => {
console.log(error);
});
}; };
const IntercomStatusForm: FC = () => { const IntercomStatusForm: FC = () => {
const { loadData, data, errorMessage } = useRest<IntercomStatus>({ read: IntercomApi.readIntercomStatus }); const { loadData, data, errorMessage } = useRest<IntercomStatus>({ read: IntercomApi.readIntercomStatus });
const { enqueueSnackbar } = useSnackbar();
const [confirmRestart, setConfirmRestart] = useState<boolean>(false);
const [processing, setProcessing] = useState<boolean>(false);
const theme = useTheme(); const theme = useTheme();
const openDoor = async () => {
setProcessing(true);
try {
await openDoorApi();
enqueueSnackbar("Door is opened", { variant: 'success' });
} catch (error: any) {
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' });
} finally {
setConfirmRestart(false);
setProcessing(false);
}
};
const content = () => { const content = () => {
if (!data) { if (!data) {
return (<FormLoader onRetry={loadData} errorMessage={errorMessage} />); return (<FormLoader onRetry={loadData} errorMessage={errorMessage} />);

View File

@ -33,7 +33,7 @@ export interface IntercomJournal {
export interface IntercomSettings { export interface IntercomSettings {
kmnModel: string; kmnModel: string;
kmnModelList: string[]; kmnModelList: string[];
firstAppartment: number; firstApartment: number;
} }
export interface SwitchDoorDTO { export interface SwitchDoorDTO {

View File

@ -5,35 +5,10 @@ import { IntercomSettings } from '../types';
import { IP_ADDRESS_VALIDATOR } from './shared'; import { IP_ADDRESS_VALIDATOR } from './shared';
export const createIntercomSettingsValidator = (intercomSettings: IntercomSettings) => new Schema({ export const createIntercomSettingsValidator = (intercomSettings: IntercomSettings) => new Schema({
provision_mode: { required: true, message: "Please provide a provision mode" }, kmnModel: [],
...({ kmnModelList: [],
ssid: [ firstApartment: [
{ required: true, message: "Please provide an SSID" }, { required: true, message: "Please provide a first appartment (for example, 1)" },
{ type: "string", max: 32, message: "SSID must be 32 characters or less" } { type: "number", message: "First appartment should be a number" }
],
password: [
{ required: true, message: "Please provide an access point password" },
{ type: "string", min: 8, max: 64, message: "Password must be 8-64 characters" }
],
channel: [
{ required: true, message: "Please provide a network channel" },
{ type: "number", message: "Channel must be between 1 and 14" }
],
max_clients: [
{ required: true, message: "Please specify a value for max clients" },
{ type: "number", min: 1, max: 9, message: "Max clients must be between 1 and 9" }
],
local_ip: [
{ required: true, message: "Local IP address is required" },
IP_ADDRESS_VALIDATOR
],
gateway_ip: [
{ required: true, message: "Gateway IP address is required" },
IP_ADDRESS_VALIDATOR
],
subnet_mask: [
{ required: true, message: "Subnet mask is required" },
IP_ADDRESS_VALIDATOR
] ]
})
}); });

File diff suppressed because it is too large Load Diff

View File

@ -24,3 +24,4 @@ lib_deps =
ottowinter/ESPAsyncWebServer-esphome@^3.1.0 ottowinter/ESPAsyncWebServer-esphome@^3.1.0
bblanchon/ArduinoJson@^6.21.3 bblanchon/ArduinoJson@^6.21.3
paulstoffregen/Time@^1.6.1 paulstoffregen/Time@^1.6.1
yutter/ArduinoJWT@^1.0.1

View File

@ -1,15 +1,20 @@
#include "app/routes.h" #include "app/routes.h"
#include "config/config.h" #include "config/config.h"
#include "utils/print.h" #include "utils/print.h"
#include "utils/settings.h" #include "utils/settings.h"
#include "utils/utils.h" #include "utils/utils.h"
#include "utils/errorCodes.h"
#include "infra/eth.h" #include "infra/eth.h"
#include "infra/httpServer.h" #include "infra/httpServer.h"
#include "infra/mqtt.h" #include "infra/mqtt.h"
#include "infra/relay.h" #include "infra/relay.h"
#include "infra/fs.h" #include "infra/fs.h"
#include "infra/intercom.h" #include "infra/intercom.h"
#include "domain/intercomJournal.h"
#include "domain/services/intercomJournal.h"
#include "domain/services/auth.h"
void handleDoorOpen(AsyncWebServerRequest *request) { void handleDoorOpen(AsyncWebServerRequest *request) {
relayTurnOn(); relayTurnOn();
@ -25,7 +30,7 @@ void getFeatures(AsyncWebServerRequest *request) {
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
root["project"] = false; root["project"] = false;
root["security"] = false; root["security"] = true;
root["mqtt"] = true; root["mqtt"] = true;
root["ntp"] = false; root["ntp"] = false;
root["ota"] = false; root["ota"] = false;
@ -47,25 +52,6 @@ void intercomStatus(AsyncWebServerRequest* request) {
request->send(response); request->send(response);
} }
static void intercomSettingsRead(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_INTERCOM_SETTINGS_SIZE);
JsonObject root = response->getRoot();
const size_t CAPACITY = JSON_ARRAY_SIZE(3);
StaticJsonDocument<CAPACITY> modelsDoc;
JsonArray models = modelsDoc.to<JsonArray>();
models.add("Vizit");
models.add("Cyfral");
root["kmnModel"] = "Vizit";
root["firstAppartment"] = 1;
root["kmnModelList"] = models;
response->setLength();
request->send(response);
}
static void intercomJournalRead(AsyncWebServerRequest* request) { static void intercomJournalRead(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_INTERCOM_JOURNAL_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_INTERCOM_JOURNAL_SIZE);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
@ -82,8 +68,24 @@ static void intercomJournalRead(AsyncWebServerRequest* request) {
request->send(response); request->send(response);
} }
static void intercomSettingsRead(AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_INTERCOM_SETTINGS_SIZE);
DynamicJsonDocument doc(1024);
bool success = readJsonVariantFromFile(INTERCOM_SETTINGS_PATH, doc);
JsonVariant root = doc.as<JsonVariant>();
if (!success)
getDefaultIntercomConf(root);
String jsonString;
serializeJson(root, jsonString);
request->send(200, "application/json", jsonString.c_str());
}
static void intercomSettingsUpdate(AsyncWebServerRequest *request, static void intercomSettingsUpdate(AsyncWebServerRequest *request,
uint8_t *data, size_t len, size_t index, size_t total) { uint8_t *data, size_t len, size_t index, size_t total) {
DynamicJsonDocument jsonDoc(MAX_INTERCOM_SETTINGS_SIZE); DynamicJsonDocument jsonDoc(MAX_INTERCOM_SETTINGS_SIZE);
String jsonStr = requestDataToStr(data, len); String jsonStr = requestDataToStr(data, len);
DeserializationError error = deserializeJson(jsonDoc, jsonStr); DeserializationError error = deserializeJson(jsonDoc, jsonStr);
@ -95,7 +97,7 @@ uint8_t *data, size_t len, size_t index, size_t total) {
JsonVariant root = jsonDoc.as<JsonVariant>(); JsonVariant root = jsonDoc.as<JsonVariant>();
bool success = writeJsonVariantToFile(INTERCOM_SETTINGS_PATH, root); bool success = writeJsonToFile(INTERCOM_SETTINGS_PATH, root);
if (success) { if (success) {
String jsonString; String jsonString;
@ -106,8 +108,8 @@ uint8_t *data, size_t len, size_t index, size_t total) {
request->send(500, "text/plain", "Intercom settings not updated"); request->send(500, "text/plain", "Intercom settings not updated");
configureIntercom( configureIntercom(
root["kmnModel"].as<String>(), root["kmnModel"],
root["firstAppartment"].as<int>() root["firstApartment"]
); );
} }
@ -173,6 +175,7 @@ static void networkSettingsRead(AsyncWebServerRequest* request) {
static void networkSettingsUpdate(AsyncWebServerRequest *request, static void networkSettingsUpdate(AsyncWebServerRequest *request,
uint8_t *data, size_t len, size_t index, size_t total) { uint8_t *data, size_t len, size_t index, size_t total) {
DynamicJsonDocument jsonDoc(MAX_NETWORK_SETTINGS_SIZE); DynamicJsonDocument jsonDoc(MAX_NETWORK_SETTINGS_SIZE);
String jsonStr = requestDataToStr(data, len); String jsonStr = requestDataToStr(data, len);
DeserializationError error = deserializeJson(jsonDoc, jsonStr); DeserializationError error = deserializeJson(jsonDoc, jsonStr);
@ -184,7 +187,7 @@ uint8_t *data, size_t len, size_t index, size_t total) {
JsonVariant root = jsonDoc.as<JsonVariant>(); JsonVariant root = jsonDoc.as<JsonVariant>();
bool success = writeJsonVariantToFile(NETWORK_SETTINGS_PATH, root); bool success = writeJsonToFile(NETWORK_SETTINGS_PATH, root);
if (success) { if (success) {
String jsonString; String jsonString;
@ -235,6 +238,7 @@ static void mqttSettingsRead(AsyncWebServerRequest* request) {
static void mqttSettingsUpdate(AsyncWebServerRequest *request, static void mqttSettingsUpdate(AsyncWebServerRequest *request,
uint8_t *data, size_t len, size_t index, size_t total) { uint8_t *data, size_t len, size_t index, size_t total) {
DynamicJsonDocument jsonDoc(MAX_MQTT_SETTINGS_SIZE); DynamicJsonDocument jsonDoc(MAX_MQTT_SETTINGS_SIZE);
String jsonStr = requestDataToStr(data, len); String jsonStr = requestDataToStr(data, len);
DeserializationError error = deserializeJson(jsonDoc, jsonStr); DeserializationError error = deserializeJson(jsonDoc, jsonStr);
@ -246,12 +250,9 @@ uint8_t *data, size_t len, size_t index, size_t total) {
JsonVariant root = jsonDoc.as<JsonVariant>(); JsonVariant root = jsonDoc.as<JsonVariant>();
bool fileLoaded = writeJsonVariantToFile(MQTT_SETTINGS_PATH, root); bool fileLoaded = writeJsonToFile(MQTT_SETTINGS_PATH, root);
bool enabled = root["enabled"].as<bool>(); bool enabled = root["enabled"].as<bool>();
if (!loadMqttConfig())
println("Cannot load MQTT config");
bool mqttConfigured = configureMqtt( bool mqttConfigured = configureMqtt(
root["enabled"].as<bool>(), root["enabled"].as<bool>(),
root["host"].as<String>(), root["host"].as<String>(),
@ -322,6 +323,53 @@ void factoryReset(AsyncWebServerRequest *request) {
restartNow(request); restartNow(request);
} }
void verifyAuthorization(AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
}
static void signIn(AsyncWebServerRequest *request,
uint8_t *data, size_t len, size_t index, size_t total) {
DynamicJsonDocument jsonDoc(MAX_LOG_IN_SIZE);
String jsonStr = requestDataToStr(data, len);
println("Access token: ", jsonStr);
DeserializationError error = deserializeJson(jsonDoc, jsonStr);
if (!jsonDoc.is<JsonVariant>() || error) {
request->send(400);
return;
}
JsonVariant root = jsonDoc.as<JsonVariant>();
String accessToken;
StatusCode status = getAccessToken(
root["username"].as<String>(),
root["password"].as<String>(),
accessToken
);
println("Access token: ", accessToken);
if (status == StatusCode::NOT_FOUND) {
request->send(403, "text/plain", "Authorization failed");
}
if (!accessToken) {
request->send(500, "text/plain", "Access token not generated");
} else {
DynamicJsonDocument jsonDoc(MAX_LOG_IN_SIZE);
JsonVariant res = jsonDoc.as<JsonVariant>();
res["access_token"] = accessToken;
String jsonString;
serializeJson(res, jsonString);
println("Sign in response: ", jsonString);
request->send(200, "application/json", jsonString.c_str());
}
}
void initRoutes() { void initRoutes() {
AsyncWebServer& server = getServer(); AsyncWebServer& server = getServer();
server.on("/api/v1/door/open", handleDoorOpen); server.on("/api/v1/door/open", handleDoorOpen);
@ -346,4 +394,7 @@ void initRoutes() {
server.on("/api/v1/systemStatus", systemStatus); server.on("/api/v1/systemStatus", systemStatus);
server.on("/api/v1/restart", restartNow); server.on("/api/v1/restart", restartNow);
server.on("/api/v1/factoryReset", factoryReset); server.on("/api/v1/factoryReset", factoryReset);
server.on("/api/v1/verifyAuthorization", HTTP_GET, verifyAuthorization);
server.on("/api/v1/signIn", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, signIn);
} }

View File

@ -16,7 +16,7 @@
#define SERIAL_NUMBER "4823" #define SERIAL_NUMBER "4823"
#define DEFAULT_OUTPUT_TOPIC_PATH "/digitum/intercom_bridge4823/out/" #define FACTORY_OUTPUT_TOPIC_PATH "/digitum/intercom_bridge4823/out/"
#define JSON_TOPIC_PATH "/digitum/intercom_bridge4823/out/json" #define JSON_TOPIC_PATH "/digitum/intercom_bridge4823/out/json"
#define MAC_ADDRESS_MQTT_TOPIC "mac" #define MAC_ADDRESS_MQTT_TOPIC "mac"
@ -48,8 +48,17 @@
#define MAX_MQTT_STATUS_SIZE 1024 #define MAX_MQTT_STATUS_SIZE 1024
#define MAX_MQTT_SETTINGS_SIZE 1024 #define MAX_MQTT_SETTINGS_SIZE 1024
#define MAX_ESP_STATUS_SIZE 1024 #define MAX_ESP_STATUS_SIZE 1024
#define MAX_LOG_IN_SIZE 1024
#define FS_CONFIG_DIRECTORY "/config" #define FS_CONFIG_DIRECTORY "/config"
#define NETWORK_SETTINGS_PATH "/config/networkSettings.json" #define NETWORK_SETTINGS_PATH "/config/networkSettings.json"
#define INTERCOM_SETTINGS_PATH "/config/intercomSettings.json" #define INTERCOM_SETTINGS_PATH "/config/intercomSettings.json"
#define MQTT_SETTINGS_PATH "/config/mqttSettings.json" #define MQTT_SETTINGS_PATH "/config/mqttSettings.json"
#define USERS_PATH "/config/users.json"
#define JWT_SECRET_KEY "secret"
#define FACTORY_ADMIN_USERNAME "admin"
#define FACTORY_ADMIN_PASSWORD "admin"
#define FACTORY_KMN_MODEL "Vizit"
#define FACTORY_FIRST_APARTMENT 1

View File

@ -0,0 +1,8 @@
#pragma once
#include <Arduino.h>
struct User {
String username;
bool admin;
};

View File

@ -0,0 +1,114 @@
#include "domain/repos/userRepo.h"
#include "config/config.h"
#include "infra/fs.h"
#define MAX_USERS_SIZE 10
#define USER_SIZE 1024
User _jsonVariantToUser(const JsonVariant& json) {
User user;
user.username = json["username"].as<String>();
user.admin = json["admin"].as<String>();
return user;
}
StatusCode createUser(User user) {
DynamicJsonDocument doc(USER_SIZE);
readJsonVariantFromFile(USERS_PATH, doc);
JsonArray root = doc.as<JsonArray>();
for (const JsonVariant& userObj: root)
if (userObj["username"] == user.username)
return StatusCode::CONFLICT;
DynamicJsonDocument userDoc(USER_SIZE);
JsonVariant userVariant = userDoc.as<JsonVariant>();
userVariant["username"] = user.username;
userVariant["admin"] = user.admin;
root.add(userVariant);
bool fileLoaded = writeJsonToFile(USERS_PATH, doc.as<JsonVariant>());
return fileLoaded ? StatusCode::OK : StatusCode::INTERNAL_SERVER_ERROR;
}
User getFactoryUser() {
return User{.username = FACTORY_ADMIN_USERNAME, .admin = FACTORY_ADMIN_PASSWORD};
}
User getUser(String username) {
DynamicJsonDocument doc(USER_SIZE);
if (!readJsonVariantFromFile(USERS_PATH, doc)) {
User factoryUser = getFactoryUser();
if (factoryUser.username == username)
return factoryUser;
else
return User();
}
JsonArray root = doc.as<JsonArray>();
for (const JsonVariant& userObj: root)
if (userObj["username"] == username)
return _jsonVariantToUser(userObj);
return User();
}
String getUsersJson() {
DynamicJsonDocument doc(USER_SIZE*MAX_USERS_SIZE);
if (!readJsonVariantFromFile(USERS_PATH, doc))
return "{}";
JsonArray root = doc.as<JsonArray>();
String jsonString;
serializeJson(root, jsonString);
return jsonString;
}
StatusCode deleteUser(String username) {
DynamicJsonDocument doc(USER_SIZE);
if (!readJsonVariantFromFile(USERS_PATH, doc))
return StatusCode::INTERNAL_SERVER_ERROR;
JsonArray root = doc.as<JsonArray>();
for (JsonArray::iterator it = root.begin(); it != root.end(); ++it) {
if ((*it)["username"] == username) {
root.remove(it);
bool fileLoaded = writeJsonToFile(USERS_PATH, doc.as<JsonVariant>());
return fileLoaded ? StatusCode::OK : StatusCode::INTERNAL_SERVER_ERROR;
}
}
return StatusCode::NOT_FOUND;
}
StatusCode updateUser(String username, User user) {
DynamicJsonDocument doc(USER_SIZE);
if (!readJsonVariantFromFile(USERS_PATH, doc))
return StatusCode::NOT_FOUND;
JsonArray root = doc.as<JsonArray>();
for (const JsonVariant& userObj: root) {
if (userObj["username"] == username) {
userObj["admin"] = user.admin;
bool fileLoaded = writeJsonToFile(USERS_PATH, doc.as<JsonVariant>());
return fileLoaded ? StatusCode::OK : StatusCode::INTERNAL_SERVER_ERROR;
}
}
return StatusCode::NOT_FOUND;
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
#include "utils/errorCodes.h"
#include "domain/services/auth.h"
#include "domain/entities/user.h"
StatusCode createUser(User user);
User getUser(String username);
String getUsersJson();
StatusCode deleteUser(String username);
StatusCode updateUser(String username, User user);

View File

@ -0,0 +1,26 @@
#include "domain/services/auth.h"
#include "domain/entities/user.h"
#include "domain/repos/userRepo.h"
#include "config/config.h"
#include "utils/print.h"
ArduinoJWT jwtService(JWT_SECRET_KEY);
StatusCode getAccessToken(String username, String password, String& accessToken) {
DynamicJsonDocument jsonDoc(MAX_LOG_IN_SIZE);
JsonVariant root = jsonDoc.as<JsonVariant>();
User user = getUser(username);
if (user.username == "")
return StatusCode::NOT_FOUND;
root["username"] = username;
root["admin"] = user.admin;
String jsonString;
serializeJson(root, jsonString);
accessToken = jwtService.encodeJWT(jsonString);
return StatusCode::OK;
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ArduinoJWT.h>
#include "utils/errorCodes.h"
StatusCode getAccessToken(String username, String password, String& accessToken);

View File

@ -1,4 +1,4 @@
#include "domain/intercomJournal.h" #include "domain/services/intercomJournal.h"
#include "utils/print.h" #include "utils/print.h"
IntercomJournal _intercomJournal; IntercomJournal _intercomJournal;

View File

@ -1,3 +1,5 @@
#pragma once
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>

View File

@ -1,4 +1,6 @@
#include "domain/stateMachineController.h" #include "domain/stateMachineController.h"
#include "strategies/cyfralStrategy.h"
#include "strategies/vizitStrategy.h"
StateMachineController StateMachineController::instance; StateMachineController StateMachineController::instance;
StateMachineController& stateMachineController = StateMachineController::getInstance(); StateMachineController& stateMachineController = StateMachineController::getInstance();
@ -43,3 +45,14 @@ int StateMachineController::getLastCalledNumber() {
DoorStatus getDoorStatus() { DoorStatus getDoorStatus() {
return DoorStatus::CLOSED; return DoorStatus::CLOSED;
} }
void StateMachineController::configure(KMNModel kmnModel, int firstApartment) {
switch (kmnModel) {
case KMNModel::CYFRAL:
this->setStrategy(new CyfralStrategy());
break;
case KMNModel::VIZIT:
this->setStrategy(new VizitStrategy());
break;
}
}

View File

@ -21,4 +21,6 @@ public:
IntercomConnectionStatus getStatus(); IntercomConnectionStatus getStatus();
int getLastCalledNumber(); int getLastCalledNumber();
DoorStatus getDoorStatus(); DoorStatus getDoorStatus();
void configure(KMNModel kmnModel, int firstApartment);
}; };

View File

@ -1,5 +1,5 @@
#include "domain/strategies/cyfralStrategy.h" #include "domain/strategies/cyfralStrategy.h"
#include "domain/intercomJournal.h" #include "domain/services/intercomJournal.h"
CyfralStrategy::CyfralStrategy() {} CyfralStrategy::CyfralStrategy() {}

View File

@ -1,5 +1,5 @@
#include "domain/strategies/vizitStrategy.h" #include "domain/strategies/vizitStrategy.h"
#include "domain/intercomJournal.h" #include "domain/services/intercomJournal.h"
VizitStrategy::VizitStrategy() {} VizitStrategy::VizitStrategy() {}

View File

@ -50,7 +50,7 @@ bool createFolderIfNotExists(const char* file_path) {
return LittleFS.mkdir(folder); return LittleFS.mkdir(folder);
} }
bool writeJsonVariantToFile(const char* file_path, JsonVariant& jsonVariant) { bool writeJsonToFile(const char* file_path, const JsonVariant& jsonValue) {
createFolderIfNotExists(file_path); createFolderIfNotExists(file_path);
File file = LittleFS.open(file_path, "w"); File file = LittleFS.open(file_path, "w");
@ -61,7 +61,7 @@ bool writeJsonVariantToFile(const char* file_path, JsonVariant& jsonVariant) {
} }
String jsonString; String jsonString;
serializeJson(jsonVariant, jsonString); serializeJson(jsonValue, jsonString);
println("Writing config: ", file_path, ", content: ", jsonString); println("Writing config: ", file_path, ", content: ", jsonString);
file.print(jsonString); file.print(jsonString);

View File

@ -4,5 +4,5 @@
void initFileSystem(); void initFileSystem();
bool readJsonVariantFromFile(const char* filename, DynamicJsonDocument& jsonDoc); bool readJsonVariantFromFile(const char* filename, DynamicJsonDocument& jsonDoc);
bool writeJsonVariantToFile(const char* filename, JsonVariant& jsonObj); bool writeJsonToFile(const char* file_path, const JsonVariant& jsonValue);
void deleteFilesInDir(const char* path); void deleteFilesInDir(const char* path);

View File

@ -1,9 +1,48 @@
#include "infra/intercom.h" #include "infra/intercom.h"
#include "infra/fs.h"
#include "utils/print.h"
extern StateMachineController& stateMachineController; extern StateMachineController& stateMachineController;
void configureIntercom(String kmnModel, int firstAppartment) { void configureIntercom(String kmnModel, int firstApartment) {
KMNModel model;
if (kmnModel == "Vizit") {
model = KMNModel::VIZIT;
} else if (kmnModel == "Cyfral") {
model = KMNModel::CYFRAL;
} else {
println("Wrong intercom model: ", kmnModel);
return;
}
stateMachineController.configure(model, firstApartment);
println("Intercom configured: ", kmnModel, ", first appartment: ", firstApartment);
}
bool loadIntercomConfig() {
DynamicJsonDocument doc(1024);
bool success = readJsonVariantFromFile(INTERCOM_SETTINGS_PATH, doc);
if (!success)
return false;
JsonVariant root = doc.as<JsonVariant>();
configureIntercom(root["kmnModel"], root["firstApartment"]);
return true;
}
void initIntercom() {
if (!loadIntercomConfig() && FACTORY_STATIC_LOCAL_IP) {
configureIntercom(
FACTORY_KMN_MODEL,
FACTORY_FIRST_APARTMENT
);
}
} }
IntercomConnectionStatus getIntercomStatus() { IntercomConnectionStatus getIntercomStatus() {
@ -13,3 +52,20 @@ IntercomConnectionStatus getIntercomStatus() {
int getLastCalledNumber() { int getLastCalledNumber() {
return stateMachineController.getLastCalledNumber(); return stateMachineController.getLastCalledNumber();
} }
JsonArray getIntercomModels() {
const size_t CAPACITY = JSON_ARRAY_SIZE(3);
StaticJsonDocument<CAPACITY> modelsDoc;
JsonArray models = modelsDoc.to<JsonArray>();
models.add("Vizit");
models.add("Cyfral");
return models;
}
void getDefaultIntercomConf(JsonVariant& root) {
root["kmnModel"] = FACTORY_KMN_MODEL;
root["firstApartment"] = FACTORY_FIRST_APARTMENT;
root["kmnModelList"] = getIntercomModels();
}

View File

@ -1,6 +1,11 @@
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h>
#include "domain/stateMachineController.h" #include "domain/stateMachineController.h"
void configureIntercom(String kmnModel, int firstAppartment); void configureIntercom(String kmnModel, int firstApartment);
bool loadIntercomConfig();
void initIntercom();
IntercomConnectionStatus getIntercomStatus(); IntercomConnectionStatus getIntercomStatus();
int getLastCalledNumber(); int getLastCalledNumber();
void getDefaultIntercomConf(JsonVariant& root);

View File

@ -8,10 +8,9 @@ AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer; TimerHandle_t mqttReconnectTimer;
AsyncMqttClientDisconnectReason mqttDisconnectReason; AsyncMqttClientDisconnectReason mqttDisconnectReason;
DynamicJsonDocument mqttConf(1024);
String mqttOutputJson = ""; String mqttOutputJson = "";
bool mqttConnected = false; bool mqttConnected = false;
bool mqttEnabled = true; bool mqttEnabled = false;
// Pointers to hold retained copies of the mqtt client connection strings. // Pointers to hold retained copies of the mqtt client connection strings.
// This is required as AsyncMqttClient holds refrences to the supplied connection strings. // This is required as AsyncMqttClient holds refrences to the supplied connection strings.
@ -36,7 +35,7 @@ void publishJSONToMQTT(const char* topic, T message) {
} }
const char* getFullTopic(const char* topic) { const char* getFullTopic(const char* topic) {
return (String(DEFAULT_OUTPUT_TOPIC_PATH) + String(topic)).c_str(); return (String(FACTORY_OUTPUT_TOPIC_PATH) + String(topic)).c_str();
} }
void publishToMQTT(const char* topic, const char* message) { void publishToMQTT(const char* topic, const char* message) {
@ -245,16 +244,14 @@ void getDefaultMqttConf(JsonVariant& root) {
} }
bool loadMqttConfig() { bool loadMqttConfig() {
bool success = readJsonVariantFromFile(MQTT_SETTINGS_PATH, mqttConf);
if (!success)
return false;
} }
void reconnectMQTTIfNeeded() { void reconnectMQTTIfNeeded() {
if (!mqttClient.connected()) { if (!mqttClient.connected()) {
if (mqttConf.isNull()) DynamicJsonDocument mqttConf(1024);
loadMqttConfig(); if (!readJsonVariantFromFile(MQTT_SETTINGS_PATH, mqttConf))
return;
JsonVariant root = mqttConf.as<JsonVariant>(); JsonVariant root = mqttConf.as<JsonVariant>();

View File

@ -21,7 +21,6 @@ AsyncMqttClient& getMqttClient();
AsyncMqttClientDisconnectReason& getMqttDisconnectReason(); AsyncMqttClientDisconnectReason& getMqttDisconnectReason();
bool getMqttConnected(); bool getMqttConnected();
bool getMqttEnabled(); bool getMqttEnabled();
bool loadMqttConfig();
bool configureMqtt( bool configureMqtt(
bool enabled, bool enabled,
String host, String host,

View File

@ -4,6 +4,7 @@
enum class SwitchDoorType { ON_OFF, JOGGING, DELAY }; enum class SwitchDoorType { ON_OFF, JOGGING, DELAY };
enum class DoorStatus { OPENED, CLOSED }; enum class DoorStatus { OPENED, CLOSED };
enum class KMNModel { CYFRAL, VIZIT };
void relayTurnOn(); void relayTurnOn();
void relayTurnOff(); void relayTurnOff();

View File

@ -15,6 +15,7 @@
#include "infra/relay.h" #include "infra/relay.h"
#include "infra/fs.h" #include "infra/fs.h"
#include "infra/httpServer.h" #include "infra/httpServer.h"
#include "infra/intercom.h"
#include "app/routes.h" #include "app/routes.h"
@ -31,7 +32,6 @@ int zeros = 0;
int ones = 0; int ones = 0;
extern StateMachineController& stateMachineController; extern StateMachineController& stateMachineController;
CyfralStrategy strategy;
void IRAM_ATTR one() { void IRAM_ATTR one() {
flag = true; flag = true;
@ -71,8 +71,7 @@ void setup() {
initMQTT(); initMQTT();
initRoutes(); initRoutes();
initHttpServer(); initHttpServer();
initIntercom();
stateMachineController.setStrategy(&strategy);
} }
void loop() { void loop() {

8
src/utils/errorCodes.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
enum class StatusCode {
OK,
NOT_FOUND,
CONFLICT,
INTERNAL_SERVER_ERROR
};

View File

@ -42,7 +42,7 @@ String Time::getFormattedTimeAgo(unsigned long timeMillis) const {
struct tm *timeStruct = gmtime(&currentTime); struct tm *timeStruct = gmtime(&currentTime);
if (timeStruct->tm_yday >= 0) { if (timeStruct->tm_yday > 0) {
return String(timeStruct->tm_yday) + " days ago"; return String(timeStruct->tm_yday) + " days ago";
} else if (timeStruct->tm_hour > 0) { } else if (timeStruct->tm_hour > 0) {
return String(timeStruct->tm_hour) + " hours ago"; return String(timeStruct->tm_hour) + " hours ago";