diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDevicesForm.tsx index 9db3de44a..b0d6ca421 100644 --- a/interface/src/project/EMSESPDevicesForm.tsx +++ b/interface/src/project/EMSESPDevicesForm.tsx @@ -45,16 +45,19 @@ import { Device, DeviceValue, DeviceValueUOM, - DeviceValueUOM_s + DeviceValueUOM_s, + Sensor } from './EMSESPtypes'; import ValueForm from './ValueForm'; +import SensorForm from './SensorForm'; import { ENDPOINT_ROOT } from '../api'; export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + 'scanDevices'; export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + 'deviceData'; export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + 'writeValue'; +export const WRITE_SENSOR_ENDPOINT = ENDPOINT_ROOT + 'writeSensor'; const StyledTableCell = withStyles((theme: Theme) => createStyles({ @@ -94,6 +97,7 @@ interface EMSESPDevicesFormState { deviceData?: EMSESPDeviceData; selectedDevice?: number; edit_devicevalue?: DeviceValue; + edit_sensorname?: Sensor; } type EMSESPDevicesFormProps = RestFormProps & @@ -215,6 +219,65 @@ class EMSESPDevicesForm extends Component< this.setState({ edit_devicevalue: dv }); }; + handleSensorChange = (name: keyof Sensor) => ( + event: React.ChangeEvent + ) => { + this.setState({ + edit_sensorname: { + ...this.state.edit_sensorname!, + [name]: extractEventValue(event) + } + }); + }; + + cancelEditingSensor = () => { + this.setState({ edit_sensorname: undefined }); + }; + + doneEditingSensor = () => { + const { edit_sensorname } = this.state; + + redirectingAuthorizedFetch(WRITE_SENSOR_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ + sensorname: edit_sensorname + }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.status === 200) { + this.props.enqueueSnackbar('Sensorname set', { + variant: 'success' + }); + } else if (response.status === 204) { + this.props.enqueueSnackbar('Sensorname not set', { + variant: 'error' + }); + } else if (response.status === 403) { + this.props.enqueueSnackbar('Write access denied', { + variant: 'error' + }); + } else { + throw Error('Unexpected response code: ' + response.status); + } + }) + .catch((error) => { + this.props.enqueueSnackbar(error.message || 'Problem writing value', { + variant: 'error' + }); + }); + + if (edit_sensorname) { + this.setState({ edit_sensorname: undefined }); + } + }; + + sendSensor = (sn: Sensor) => { + this.setState({ edit_sensorname: sn }); + }; + noDevices = () => { return this.props.data.devices.length === 0; }; @@ -298,28 +361,47 @@ class EMSESPDevicesForm extends Component< renderSensorItems() { const { data } = this.props; + const me = this.props.authenticatedContext.me; return (

- Dallas Sensors + Sensors {!this.noSensors() && ( + Sensor # - ID + ID / Name Temperature {data.sensors.map((sensorData) => ( - + + + {me.admin && ( + + this.sendSensor(sensorData)} + > + + + + )} + {sensorData.no} - {sensorData.id} + {sensorData.id} {formatValue(sensorData.temp, DeviceValueUOM.DEGREES, 1)} @@ -339,6 +421,34 @@ class EMSESPDevicesForm extends Component< ); } + renderAnalog() { + const { data } = this.props; + return ( + + {data.analog > 0 && ( +
+ + + Sensortype + Value + + + + + + Analog Input + + + {formatValue(data.analog, DeviceValueUOM.MV, 0)} + + + +
+ )} +
+ ); + } + renderScanDevicesDialog() { return (

{this.renderDeviceItems()} {this.renderDeviceData()} {this.renderSensorItems()} + {this.renderAnalog()}

@@ -542,6 +653,14 @@ class EMSESPDevicesForm extends Component< handleValueChange={this.handleValueChange} /> )} + {edit_sensorname && ( + + )} ); } diff --git a/interface/src/project/EMSESPtypes.ts b/interface/src/project/EMSESPtypes.ts index f6b9bbf7a..18e306f27 100644 --- a/interface/src/project/EMSESPtypes.ts +++ b/interface/src/project/EMSESPtypes.ts @@ -60,6 +60,7 @@ export interface Sensor { export interface EMSESPDevices { devices: Device[]; sensors: Sensor[]; + analog: number; } export interface DeviceValue { @@ -93,7 +94,8 @@ export enum DeviceValueUOM { DBM, NUM, BOOLEAN, - LIST + LIST, + MV } export const DeviceValueUOM_s = [ @@ -114,5 +116,6 @@ export const DeviceValueUOM_s = [ 'dBm', 'number', 'on/off', - '' + '', + 'mV' ]; diff --git a/interface/src/project/SensorForm.tsx b/interface/src/project/SensorForm.tsx new file mode 100644 index 000000000..fa6ee246d --- /dev/null +++ b/interface/src/project/SensorForm.tsx @@ -0,0 +1,92 @@ +import React, { RefObject } from 'react'; +import { ValidatorForm } from 'react-material-ui-form-validator'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + FormHelperText, + OutlinedInput +} from '@material-ui/core'; + +import { FormButton } from '../components'; +import { Sensor } from './EMSESPtypes'; + +interface SensorFormProps { + sensor: Sensor; + onDoneEditing: () => void; + onCancelEditing: () => void; + handleSensorChange: ( + data: keyof Sensor + ) => (event: React.ChangeEvent) => void; +} + +class SensorForm extends React.Component { + formRef: RefObject = React.createRef(); + + submit = () => { + this.formRef.current.submit(); + }; + + render() { + const { + sensor, + handleSensorChange, + onDoneEditing, + onCancelEditing + } = this.props; + + return ( + + + + Change Sensor Name + + + + Name of sensor #{sensor.no} + + + + (optional 'offset' separated by space) + + + + + Cancel + + + Done + + + + + ); + } +} + +export default SensorForm; diff --git a/mock-api/server.js b/mock-api/server.js index c77aad7ef..947a24e04 100644 --- a/mock-api/server.js +++ b/mock-api/server.js @@ -282,6 +282,7 @@ const EMSESP_DEVICEDATA_ENDPOINT = REST_ENDPOINT_ROOT + 'deviceData' const EMSESP_STATUS_ENDPOINT = REST_ENDPOINT_ROOT + 'emsespStatus' const EMSESP_BOARDPROFILE_ENDPOINT = REST_ENDPOINT_ROOT + 'boardProfile' const WRITE_VALUE_ENDPOINT = REST_ENDPOINT_ROOT + 'writeValue' ++const WRITE_SENSOR_ENDPOINT = REST_ENDPOINT_ROOT + 'writeSensor' const emsesp_settings = { tx_mode: 1, tx_delay: 0, diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index b23eb898f..ae78d39a5 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -310,10 +310,10 @@ std::string DallasSensor::Sensor::id_string() const { return str; } -std::string DallasSensor::Sensor::to_string() const { +std::string DallasSensor::Sensor::to_string(const bool name) const { std::string str = id_string(); EMSESP::webSettingsService.read([&](WebSettings & settings) { - if (settings.dallas_format == Dallas_Format::NAME) { + if (settings.dallas_format == Dallas_Format::NAME || name) { for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { if (strcmp(settings.sensor[i].id.c_str(), str.c_str()) == 0) { str = settings.sensor[i].name.c_str(); @@ -339,7 +339,24 @@ int16_t DallasSensor::Sensor::offset() const { return offset; } -void DallasSensor::add_name(const char * id, const char * name, int16_t offset) { +bool DallasSensor::add_name(const char * idstr, const char * name, int16_t offset) { + bool ok = false; + char id[20]; + strlcpy(id, idstr, sizeof(id)); + + // check for number and convert to id + if (strlen(id) > 0 && strlen(id) <= 2 && id[0] >= '1' && id[0] <= '9') { + uint8_t no = atoi(idstr) - 1; + if (no < sensors_.size()) { + strlcpy(id, sensors_[no].id_string().c_str(), sizeof(id)); + } + } + // check valid id + if (strlen(id) != 17 || id[2] != '-' || id[7] != '-' || id[12] !='-') { + LOG_WARNING(F("Invalid sensor id: %s"), id); + return ok; + } + EMSESP::webSettingsService.update([&](WebSettings & settings) { // check for new name of stored id for (uint8_t i = 0; i < NUM_SENSOR_NAMES; i++) { @@ -354,6 +371,7 @@ void DallasSensor::add_name(const char * id, const char * name, int16_t offset) settings.sensor[i].offset = offset; LOG_INFO(F("Setting name of sensor %s to %s"), id, name); } + ok = true; return StateUpdateResult::CHANGED; } } @@ -364,6 +382,7 @@ void DallasSensor::add_name(const char * id, const char * name, int16_t offset) settings.sensor[i].name = (strlen(name) == 0) ? id : name; settings.sensor[i].offset = offset; LOG_INFO(F("Setting name of sensor %s to %s"), id, name); + ok = true; return StateUpdateResult::CHANGED; } } @@ -380,12 +399,14 @@ void DallasSensor::add_name(const char * id, const char * name, int16_t offset) settings.sensor[i].name = (strlen(name) == 0) ? id : name; settings.sensor[i].offset = offset; LOG_INFO(F("Setting name of sensor %s to %s"), id, name); + ok = true; return StateUpdateResult::CHANGED; } } LOG_ERROR(F("List full, remove one sensorname first")); return StateUpdateResult::UNCHANGED; }, "local"); + return ok; } // check to see if values have been updated diff --git a/src/dallassensor.h b/src/dallassensor.h index ec410d0e8..6d469dd04 100644 --- a/src/dallassensor.h +++ b/src/dallassensor.h @@ -47,7 +47,7 @@ class DallasSensor { uint64_t id() const; std::string id_string() const; - std::string to_string() const; + std::string to_string(const bool name = false) const; int16_t offset() const; int16_t temperature_c = EMS_VALUE_SHORT_NOTSET; @@ -88,7 +88,7 @@ class DallasSensor { dallas_format_ = dallas_format; } - void add_name(const char * id, const char * name, int16_t offset); + bool add_name(const char * idstr, const char * name, int16_t offset); private: static constexpr uint8_t MAX_SENSORS = 20; @@ -134,7 +134,6 @@ class DallasSensor { bool command_commands(const char * value, const int8_t id, JsonObject & json); uint32_t last_activity_ = uuid::get_uptime(); - uint32_t last_publish_ = uuid::get_uptime(); State state_ = State::IDLE; std::vector sensors_; diff --git a/src/system.h b/src/system.h index d786ad7f7..d4b784932 100644 --- a/src/system.h +++ b/src/system.h @@ -81,6 +81,14 @@ class System { static bool is_valid_gpio(uint8_t pin); static bool load_board_profile(std::vector & data, const std::string & board_profile); + bool analog_enabled() { + return analog_enabled_; + } + + uint16_t analog() { + return analog_; + } + std::string hostname() { return hostname_; } @@ -96,6 +104,7 @@ class System { void ethernet_connected(bool b) { ethernet_connected_ = b; } + void network_connected(bool b) { network_connected_ = b; } diff --git a/src/web/WebDevicesService.cpp b/src/web/WebDevicesService.cpp index f5ed5afab..e5bb70911 100644 --- a/src/web/WebDevicesService.cpp +++ b/src/web/WebDevicesService.cpp @@ -26,7 +26,9 @@ WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * : _device_dataHandler(DEVICE_DATA_SERVICE_PATH, securityManager->wrapCallback(std::bind(&WebDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) , _writevalue_dataHandler(WRITE_VALUE_SERVICE_PATH, - securityManager->wrapCallback(std::bind(&WebDevicesService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { + securityManager->wrapCallback(std::bind(&WebDevicesService::write_value, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) + , _writesensor_dataHandler(WRITE_SENSOR_SERVICE_PATH, + securityManager->wrapCallback(std::bind(&WebDevicesService::write_sensor, this, _1, _2), AuthenticationPredicates::IS_ADMIN)) { server->on(EMSESP_DEVICES_SERVICE_PATH, HTTP_GET, securityManager->wrapRequest(std::bind(&WebDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED)); @@ -41,6 +43,10 @@ WebDevicesService::WebDevicesService(AsyncWebServer * server, SecurityManager * _writevalue_dataHandler.setMethod(HTTP_POST); _writevalue_dataHandler.setMaxContentLength(256); server->addHandler(&_writevalue_dataHandler); + + _writesensor_dataHandler.setMethod(HTTP_POST); + _writesensor_dataHandler.setMaxContentLength(256); + server->addHandler(&_writesensor_dataHandler); } void WebDevicesService::scan_devices(AsyncWebServerRequest * request) { @@ -73,10 +79,13 @@ void WebDevicesService::all_devices(AsyncWebServerRequest * request) { for (const auto & sensor : EMSESP::sensor_devices()) { JsonObject obj = sensors.createNestedObject(); obj["no"] = i++; - obj["id"] = sensor.to_string(); + obj["id"] = sensor.to_string(true); obj["temp"] = Helpers::render_value(s, sensor.temperature_c, 10); } } + if (EMSESP::system_.analog_enabled()) { + root["analog"] = EMSESP::system_.analog(); + } response->setLength(); request->send(response); @@ -146,4 +155,34 @@ void WebDevicesService::write_value(AsyncWebServerRequest * request, JsonVariant request->send(response); } +// takes a sensorname and optional offset from the Web +void WebDevicesService::write_sensor(AsyncWebServerRequest * request, JsonVariant & json) { + bool ok = false; + if (json.is()) { + JsonObject sn = json["sensorname"]; + std::string id = sn["id"]; + uint8_t no = sn["no"]; + char nostr[3]; + char name[25]; + int16_t offset = 0; + + strlcpy(name, id.c_str(), sizeof(name)); + if (no > 0 && no < 100) { + itoa(no, nostr, 10); + char * c = strchr(name, ' '); // find space + if (c != nullptr) { + *c = '\0'; + float v; + if (Helpers::value2float((c + 1), v)) { + offset = v * 10; + } + } + ok = EMSESP::dallassensor_.add_name(nostr, name, offset); + } + } + + AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 204); + request->send(response); +} + } // namespace emsesp \ No newline at end of file diff --git a/src/web/WebDevicesService.h b/src/web/WebDevicesService.h index a5bbe87e7..00f12c9a4 100644 --- a/src/web/WebDevicesService.h +++ b/src/web/WebDevicesService.h @@ -28,6 +28,7 @@ #define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices" #define DEVICE_DATA_SERVICE_PATH "/rest/deviceData" #define WRITE_VALUE_SERVICE_PATH "/rest/writeValue" +#define WRITE_SENSOR_SERVICE_PATH "/rest/writeSensor" namespace emsesp { @@ -43,8 +44,9 @@ class WebDevicesService { // POST void device_data(AsyncWebServerRequest * request, JsonVariant & json); void write_value(AsyncWebServerRequest * request, JsonVariant & json); + void write_sensor(AsyncWebServerRequest * request, JsonVariant & json); - AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler; + AsyncCallbackJsonWebHandler _device_dataHandler, _writevalue_dataHandler, _writesensor_dataHandler; }; } // namespace emsesp