diff --git a/interface/src/app/main/Dashboard.tsx b/interface/src/app/main/Dashboard.tsx index 061fa8165..002727f52 100644 --- a/interface/src/app/main/Dashboard.tsx +++ b/interface/src/app/main/Dashboard.tsx @@ -3,7 +3,6 @@ import { IconContext } from 'react-icons/lib'; import { toast } from 'react-toastify'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import CircleIcon from '@mui/icons-material/Circle'; import EditIcon from '@mui/icons-material/Edit'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; @@ -27,7 +26,7 @@ import { AuthenticatedContext } from 'contexts/authentication'; import { useI18nContext } from 'i18n/i18n-react'; import { useInterval } from 'utils'; -import { readDashboard, writeDeviceValue, writeSchedule } from '../../api/app'; +import { readDashboard, writeDeviceValue } from '../../api/app'; import DeviceIcon from './DeviceIcon'; import DevicesDialog from './DevicesDialog'; import { formatValue } from './deviceValue'; @@ -35,8 +34,7 @@ import { type DashboardItem, DeviceEntityMask, DeviceType, - type DeviceValue, - type Schedule + type DeviceValue } from './types'; import { deviceValueItemValidation } from './validators'; @@ -63,13 +61,6 @@ const Dashboard = () => { initialData: [] }); - const { send: updateSchedule } = useRequest( - (data: Schedule) => writeSchedule(data), - { - immediate: false - } - ); - const { loading: submitting, send: sendDeviceValue } = useRequest( (data: { id: number; c: string; v: unknown }) => writeDeviceValue(data), { @@ -114,6 +105,11 @@ const Dashboard = () => { border-top: 1px solid #177ac9; border-bottom: 1px solid #177ac9; } + `, + BaseCell: ` + &:nth-of-type(2) { + text-align: right; + } ` }); @@ -196,39 +192,13 @@ const Dashboard = () => { } } if (di.dv) { - return ( - // ids for scheduler, and sensors are between 9600 and 9900 - - {di.id >= 9600 && di.id < 9900 ? di.dv.id : di.dv.id.slice(2)} - - ); + return {di.dv.id.slice(2)}; } }; const hasMask = (id: string, mask: number) => (parseInt(id.slice(0, 2), 16) & mask) === mask; - const toggleSchedule = async (di: DashboardItem) => { - // create a dummy record, the id=0 picks it up - await updateSchedule({ - schedule: { - id: 0, // special number for only changing the active flag - active: !di.dv?.v, - flags: 0, // unused - time: '', // unused - cmd: '', // unused - value: '', // unused - name: di.dv?.id ?? '' - } - }) - .catch((error: Error) => { - toast.error(error.message); - }) - .finally(async () => { - await fetchDashboard(); - }); - }; - const editDashboardValue = (di: DashboardItem) => { setSelectedDashboardItem(di); setDeviceValueDialogOpen(true); @@ -325,25 +295,7 @@ const Dashboard = () => { - {me.admin && di.id < 9700 && di.id >= 9600 ? ( - toggleSchedule(di)} - > - {di.dv?.v ? ( - - ) : ( - - )} - - ) : ( - me.admin && + {me.admin && di.dv?.c && !hasMask(di.dv.id, DeviceEntityMask.DV_READONLY) && ( { sx={{ fontSize: 16 }} /> - ) - )} + )} ) : ( diff --git a/interface/src/app/main/DeviceIcon.tsx b/interface/src/app/main/DeviceIcon.tsx index ad921aacf..b216cbacb 100644 --- a/interface/src/app/main/DeviceIcon.tsx +++ b/interface/src/app/main/DeviceIcon.tsx @@ -10,7 +10,8 @@ import { MdOutlineSensors, MdThermostatAuto } from 'react-icons/md'; -import { TiFlowSwitch } from 'react-icons/ti'; +import { PiFan, PiGauge } from 'react-icons/pi'; +import { TiFlowSwitch, TiThermometer } from 'react-icons/ti'; import { VscVmConnect } from 'react-icons/vsc'; import type { SvgIconProps } from '@mui/material'; @@ -20,8 +21,8 @@ import { DeviceType } from './types'; const deviceIconLookup: { [key in DeviceType]: React.ComponentType | undefined; } = { - [DeviceType.TEMPERATURESENSOR]: MdOutlineSensors, - [DeviceType.ANALOGSENSOR]: MdOutlineSensors, + [DeviceType.TEMPERATURESENSOR]: TiThermometer, + [DeviceType.ANALOGSENSOR]: PiGauge, [DeviceType.BOILER]: CgSmartHomeBoiler, [DeviceType.HEATSOURCE]: CgSmartHomeBoiler, [DeviceType.THERMOSTAT]: MdThermostatAuto, @@ -37,11 +38,11 @@ const deviceIconLookup: { [DeviceType.WATER]: GiTap, [DeviceType.POOL]: MdOutlinePool, [DeviceType.CUSTOM]: MdPlaylistAdd, - [DeviceType.UNKNOWN]: undefined, + [DeviceType.UNKNOWN]: MdOutlineSensors, [DeviceType.SYSTEM]: undefined, [DeviceType.SCHEDULER]: MdMoreTime, - [DeviceType.GENERIC]: undefined, - [DeviceType.VENTILATION]: undefined + [DeviceType.GENERIC]: MdOutlineSensors, + [DeviceType.VENTILATION]: PiFan }; const DeviceIcon = ({ type_id }: { type_id: DeviceType }) => { diff --git a/src/web/WebDataService.cpp b/src/web/WebDataService.cpp index 1aa88ac36..e06eaf556 100644 --- a/src/web/WebDataService.cpp +++ b/src/web/WebDataService.cpp @@ -78,7 +78,7 @@ void WebDataService::core_data(AsyncWebServerRequest * request) { uint8_t customEntities = EMSESP::webCustomEntityService.count_entities(); if (customEntities) { JsonObject obj = devices.add(); - obj["id"] = 99; // the last unique id + obj["id"] = EMSdevice::DeviceTypeUniqueID::CUSTOM_UID; obj["tn"] = Helpers::translated_word(FL_(custom_device)); // translated device type name obj["t"] = EMSdevice::DeviceType::CUSTOM; // device type number obj["b"] = Helpers::translated_word(FL_(na)); // brand @@ -194,7 +194,7 @@ void WebDataService::device_data(AsyncWebServerRequest * request) { } #ifndef EMSESP_STANDALONE - if (id == 99) { + if (id == EMSdevice::DeviceTypeUniqueID::CUSTOM_UID) { JsonObject output = response->getRoot(); EMSESP::webCustomEntityService.generate_value_web(output); response->setLength(); @@ -224,84 +224,76 @@ void WebDataService::write_device_value(AsyncWebServerRequest * request, JsonVar } // using the unique ID from the web find the real device type - for (const auto & emsdevice : EMSESP::emsdevices) { - if (emsdevice->unique_id() == unique_id) { - // create JSON for output - auto * response = new AsyncJsonResponse(false); - JsonObject output = response->getRoot(); - - // the data could be in any format, but we need string - // authenticated is always true - uint8_t return_code = CommandRet::NOT_FOUND; - uint8_t device_type = emsdevice->device_type(); - // parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp - int8_t id = -1; // default - if (device_type >= EMSdevice::DeviceType::BOILER) { - cmd = Command::parse_command_string(cmd, id); // extract hc or dhw + uint8_t device_type = EMSdevice::DeviceType::UNKNOWN; + switch (unique_id) { + case EMSdevice::DeviceTypeUniqueID::CUSTOM_UID: + device_type = EMSdevice::DeviceType::CUSTOM; + break; + case EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID: + device_type = EMSdevice::DeviceType::SCHEDULER; + break; + case EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID: + device_type = EMSdevice::DeviceType::TEMPERATURESENSOR; + break; + case EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID: + device_type = EMSdevice::DeviceType::ANALOGSENSOR; + break; + default: + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->unique_id() == unique_id) { + device_type = emsdevice->device_type(); + break; } - if (data.is()) { - return_code = Command::call(device_type, cmd, data.as(), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 1), true, id, output); - } else if (data.is()) { - return_code = Command::call(device_type, cmd, data.as() ? "true" : "false", true, id, output); - } - - // write log - if (return_code != CommandRet::OK) { - EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code)); - } else { -#if defined(EMSESP_DEBUG) - EMSESP::logger().debug("Write command successful"); -#endif - } - - response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request - response->setLength(); - request->send(response); - return; } + break; } - - // special check for custom entities (which have a unique id of 99) - if (unique_id == 99) { - auto * response = new AsyncJsonResponse(false); - JsonObject output = response->getRoot(); - uint8_t return_code = CommandRet::NOT_FOUND; - uint8_t device_type = EMSdevice::DeviceType::CUSTOM; - // parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp - int8_t id = -1; - if (device_type >= EMSdevice::DeviceType::BOILER) { - cmd = Command::parse_command_string(cmd, id); // extract hc or dhw - } - if (data.is()) { - return_code = Command::call(device_type, cmd, data.as(), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true, id, output); - } else if (data.is()) { - char s[10]; - return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 1), true, id, output); - } - if (return_code != CommandRet::OK) { - EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code)); - } else { -#if defined(EMSESP_DEBUG) - EMSESP::logger().debug("Write command successful"); -#endif - } - - response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request - response->setLength(); + if (device_type == EMSdevice::DeviceType::UNKNOWN) { + EMSESP::logger().warning("Write command failed, bad device id: %d", unique_id); + AsyncWebServerResponse * response = request->beginResponse(400); // bad request request->send(response); return; } + // create JSON for output + auto * response = new AsyncJsonResponse(false); + JsonObject output = response->getRoot(); + // the data could be in any format, but we need string + // authenticated is always true + uint8_t return_code = CommandRet::NOT_FOUND; + // parse the command as it could have a hc or dhw prefixed, e.g. hc2/seltemp + int8_t id = -1; // default + if (device_type >= EMSdevice::DeviceType::BOILER) { + cmd = Command::parse_command_string(cmd, id); // extract hc or dhw + } + if (data.is()) { + return_code = Command::call(device_type, cmd, data.as(), true, id, output); + } else if (data.is()) { + char s[10]; + return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 0), true, id, output); + } else if (data.is()) { + char s[10]; + return_code = Command::call(device_type, cmd, Helpers::render_value(s, data.as(), 1), true, id, output); + } else if (data.is()) { + return_code = Command::call(device_type, cmd, data.as() ? "true" : "false", true, id, output); + } + + // write log + if (return_code != CommandRet::OK) { + // is already logged by command and message contains code + // EMSESP::logger().err("Write command failed %s (%s)", (const char *)output["message"], Command::return_code_string(return_code)); + } else { +#if defined(EMSESP_DEBUG) + EMSESP::logger().debug("Write command successful"); +#endif + } + + response->setCode((return_code == CommandRet::OK) ? 200 : 400); // bad request + response->setLength(); + request->send(response); + return; } + EMSESP::logger().warning("Write command failed, bad json"); + // if we reach here, fail AsyncWebServerResponse * response = request->beginResponse(400); // bad request request->send(response); @@ -393,7 +385,7 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { node["id"] = (EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID * 100) + count++; JsonObject dv = node["dv"].to(); - dv["id"] = sensor.name(); + dv["id"] = "00" + sensor.name(); if (EMSESP::system_.fahrenheit()) { if (Helpers::hasValue(sensor.temperature_c)) { dv["v"] = (float)sensor.temperature_c * 0.18 + 32; @@ -421,9 +413,20 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++; JsonObject dv = node["dv"].to(); - dv["id"] = sensor.name(); - dv["v"] = Helpers::transformNumFloat(sensor.value(), 0); // is optional and is a float - dv["u"] = sensor.uom(); + dv["id"] = "00" + sensor.name(); + if (sensor.type() == AnalogSensor::AnalogType::DIGITAL_OUT || sensor.type() == AnalogSensor::AnalogType::DIGITAL_IN) { + char s[12]; + dv["v"] = Helpers::render_boolean(s, sensor.value() != 0, true); + JsonArray l = dv["l"].to(); + l.add(Helpers::render_boolean(s, false, true)); + l.add(Helpers::render_boolean(s, true, true)); + } else { + dv["v"] = Helpers::transformNumFloat(sensor.value(), 0); + } + if (sensor.type() == AnalogSensor::AnalogType::COUNTER || sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT) { + dv["c"] = sensor.name(); + } + dv["u"] = sensor.uom(); } } } @@ -444,8 +447,13 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { node["id"] = (EMSdevice::DeviceTypeUniqueID::SCHEDULER_UID * 100) + count++; JsonObject dv = node["dv"].to(); - dv["id"] = scheduleItem.name; - dv["v"] = scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE ? scheduleItem.active : false; // value is active + dv["id"] = "00" + scheduleItem.name; + dv["c"] = scheduleItem.name; + char s[12]; + dv["v"] = Helpers::render_boolean(s, scheduleItem.active, true); + JsonArray l = dv["l"].to(); + l.add(Helpers::render_boolean(s, false, true)); + l.add(Helpers::render_boolean(s, true, true)); } } }); @@ -462,5 +470,4 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) { request->send(response); } - } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebSchedulerService.cpp b/src/web/WebSchedulerService.cpp index dae491fa9..525367b18 100644 --- a/src/web/WebSchedulerService.cpp +++ b/src/web/WebSchedulerService.cpp @@ -65,29 +65,6 @@ void WebScheduler::read(WebScheduler & webScheduler, JsonObject root) { // call on initialization and also when the Schedule web page is saved // this loads the data into the internal class StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webScheduler) { - // check if we're only toggling it on/off via the Dashboard - // if it is a single schedule object and id is 0 - if (root["schedule"].is()) { - JsonObject si = root["schedule"]; - if (si["id"] == 0) { - // find the item, matching the name - for (ScheduleItem & scheduleItem : webScheduler.scheduleItems) { - std::string name = si["name"].as(); - if (scheduleItem.name == name) { - scheduleItem.active = si["active"]; - EMSESP::webSchedulerService.publish(true); - return StateUpdateResult::CHANGED; - } - } - return StateUpdateResult::UNCHANGED; - } - } - - // otherwise we're updating and saving the whole list again - if (!root["schedule"].is()) { - return StateUpdateResult::ERROR; // invalid format - } - // reset the list Command::erase_device_commands(EMSdevice::DeviceType::SCHEDULER); webScheduler.scheduleItems.clear();