├── .gitignore
├── PROTOCOL.md
├── README.md
├── data
├── ajax-loader.gif
├── chart.min.js.gz
├── chartjs-annotation.min.js.gz
├── docstrings.js
├── gauge.min.js.gz
├── gauges.html
├── gauges.js
├── icon-check-circle.png
├── icon-trash.png
├── icon-x-square.png
├── index.html
├── index.js
├── inverter.js
├── jquery.core.min.js.gz
├── jquery.knob.min.js.gz
├── log.html
├── log.js
├── logo.png
├── modal.js
├── plot.js
├── refresh.png
├── remote.html
├── rtc.html
├── sdcard.html
├── style.css
├── syncofs.html
├── ui.js
├── wifi-updated.html
├── wifi.html
└── wifi.js
├── doc
├── ARDUINO_IDE_setup.md
├── ARDUINO_IDE_usage.md
├── PLATFORMIO_setup.md
└── PLATFORMIO_usage.md
├── esp32-web-interface.cbp
├── esp32-web-interface.ino
├── platformio-local-override.ini.example
├── platformio.ini
└── upload.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .pio/
3 | platformio-local-override.ini
4 |
--------------------------------------------------------------------------------
/PROTOCOL.md:
--------------------------------------------------------------------------------
1 | # Openinverter Web Interface Protocol
2 |
3 | This document describes the protocol used on the serial interface between this ESP8266 module, and
4 | an inverter or VCU.
5 |
6 | ## General
7 |
8 | Commands are sent by the ESP8266. Each command consists of a single line consisting of a command word
9 | followed optionally by parameters and terminates with a newline character. Commands must be echoed back
10 | to the ESP8266.
11 |
12 | Following the echo, the esp8266 will receive an unlimited quantity of response data, terminated by a
13 | 100ms timeout.
14 |
15 | Except where otherwise noted, the responses are free text and should generally contain a single human
16 | readable line indicating success or faulure of the operation.
17 |
18 | ## Parameters and other data
19 |
20 | Openinverter makes available two types of data - parameters and non-parameters.
21 |
22 | Parameters are user configurable values, generally used for configuration. They can be stored in
23 | nonvolatile memory and should not change except in response to a user request.
24 |
25 | Other values are made available that are not configurable, but are instead indicative of the immediate
26 | state of the inverter. These are useful for monitoring and debugging.
27 |
28 | ## Commands
29 |
30 | | Command | Description|
31 | |---------|------------|
32 | |`save`|save current parameters to nonvolatile memory|
33 | |`load`|load parameters from nonvolatile memory|
34 | |`fastuart`|increases the baud rate to 921600Response must begin "OK" in success case|
35 | |`set [parameter] [value]`|set the decimal value of a named parameter|
36 | |`can [direction] [name] [canid] [offset] [length] [gain]`|map values to CAN messages|
37 | |`can clear`|clear all can mappings|
38 | |`start [opmode]`|start the inverter in a specified modemode 2 is manual run|
39 | |`stop`|stop the inverter|
40 | |`get [parameter]`|get the value of a parameter|
41 | |`stream [repetitions] [val1,val2,val3]`| repeatedly read and return one or more values|
42 | |`json [hidden]`| return an JSON encoded mapping of all parameters and values - see JSON format below|
43 | |`errors`|print information about all currently active error states, or indicate that everything is okay|
44 | |`reset`|reboot the device|
45 | |`defaults`|restore all parameters to default values|
46 |
47 | Note: This is not an exhaustive list of commands supported by openinverter devices, but does include
48 | all commands currently used by the openinverter web intrface.
49 |
50 | ## JSON Mapping
51 |
52 | The json command requests a dump of the full schema and values of both the configurable parameters
53 | and other available data. The optional "hidden" flag requests data that would not normally be
54 | displayed to the user.
55 |
56 | The following example shows a non-parameter value, a parameter value, and a value that has been
57 | mapped to CAN.
58 |
59 | ```json
60 | {
61 | "udc": {"unit":"V", "value": 400.0, "isparam": false},
62 | "fweak": {"unit":"Hz", "value": 67.0, "isparam": true, "minimum": 0.0, "maximum": 400.0, "default": 67.0, "category": "Motor (sine)", "i": 8},
63 | "speed": {"unit":"rpm", "value": 1000.0, "isparam": false, "canid": 123, "canoffset":0, "canlength":32, "cangain":5, "isrx": false}
64 | }
65 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | esp32-web-interface
2 | =====================
3 | Web interface for Huebner inverter
4 |
5 | # Table of Contents
6 |
7 | Click to open TOC
8 |
9 |
10 | - [About](#about)
11 | - [Usage](#usage)
12 | - [Wifi network](#wifi-network)
13 | - [Reaching the board](#reaching-the-board)
14 | - [Hardware](#hardware)
15 | - [Firmware](#firmware)
16 | - [Flashing / Upgrading](#flashing--upgrading)
17 | - [Wirelessly](#wirelessly)
18 | - [Wired](#wired)
19 | - [Documentations](#documentations)
20 | - [Development](#development)
21 | - [Arduino](#arduino)
22 | - [PlatformIO](#platformio)
23 |
24 |
25 |
26 |
27 | # About
28 | This repository hosts the source code for the Web Interface for the Huebner inverter, and derivated projects:
29 | * [OpenInverter Sine (and FOC) firmware](https://github.com/jsphuebner/stm32-sine)
30 | * [Vehicle Control Unit for Electric Vehicle Conversion Projects](https://github.com/damienmaguire/Stm32-vcu)
31 | * [OpenInverter buck or boost mode charger firmware](https://github.com/jsphuebner/stm32-charger)
32 | * [OpenInverter non-grid connected inverter](https://github.com/jsphuebner/stm32-island)
33 | * [BMS project firmware](https://github.com/jsphuebner/bms-software)
34 | * ...
35 |
36 | It is written with the Arduino development environment and libraries.
37 |
38 | # Usage
39 | To use the web interface 2 things are needed :
40 | * You need to have a computer on the same WiFi network as the board,
41 | * You need to 'browse' the web interface page.
42 |
43 | ## Wifi network
44 | There are 2 possibilities:
45 | * Either you connect to an Access Point generated by the board. The default name for this access point is 'ESP-xxxxx' but can be customized. In that case, the board will have a fixed IP address of `192.168.4.1` (and will be reachable on http://192.168.4.1/)
46 | * Or you can configure the board to join your own WiFi network ; and in that case you may need to tweak your network configuration to provide a fixed address to the board (not necessary).
47 |
48 | ## Reaching the board
49 | The board announces itself to the world using mDNS protocol (aka Bonjour, or Rendezvous, or Zeroconf), so you may be able to reach the board using a local name of `inverter.local`.
50 | So first try to reach it on http://inverter.local/
51 |
52 | # Hardware
53 | The web interface has been initially designed to run on ESP32-WROOM-32E boards.
54 |
55 | A SD card running in SDIO mode can be connected (CLK to pin14, CMD to pin15, D0 to Pin2, D1 to Pin4, D2 to Pin12, D3 to Pin13).
56 |
57 | A RTC can be connected. As standard a PCF8523 is suported but any clock supported by RTClib can be used with a sketch change. (SCLK to Pin22, SDA to Pin21).
58 |
59 | The connection to the inverter are on Pin16 (Rx line connect to inverter Tx line) and Pin17 (Tx line connect to inverter Rx line).
60 |
61 | # Firmware
62 | Tompile it follow the [instructions below](#development).
63 |
64 | # Flashing / Upgrading
65 | ## Wirelessly
66 | TBA
67 |
68 | ## Wired
69 | If your board is new and unprogrammed, or if you want to fully re-program it, you'll need to have a wired connection between your computer and the board.
70 | You'll either need a ESP32 board with an on board USB to serial converter or a 3.3v capable USB / Serial adapter
71 | * the following connections:
72 |
73 | Pin# | ESP32 Board Function | USB / Serial adapter
74 | ----- | ---------------------- | --------------------
75 | 1 | +3.3v input | (Some adapters provide a +3.3v output, you can use it)
76 | 2 | GND | GND
77 | 3 | RXD input | TXD output
78 | 4 | TXD output | RXD input
79 |
80 | Then you would use any of the the [development tool below](#development) ; or the `esptool.py` tool to upload either a binary firmware file, or a binary filesystem file.
81 |
82 | Various openinverter boards (SDU, LDU, Leaf) use a different wiring scheme for initial programming. It is important to flash the ESP32 chip BEFORE flashing the STM32 chip because otherwise you will get a bus collision on the UART pins. If you've already flashed the STM32 either erase the flash or hold it in reset somehow while flashing the ESP32.
83 |
84 | Pin# | ESP32 Board Function | USB / Serial adapter
85 | ----- | ---------------------- | --------------------
86 | 1 | TXD output | RXD input
87 | 2 | RXD input | TXD output
88 | 3 | +5v input | (Some adapters provide a +5v output, you can use it)
89 | 4 | GND | GND
90 | 5 | GND | GND
91 | 6 | GPIO0 | Connect this to pin 5 (GND) to put the ESP32 into programming mode. Then power up
92 |
93 | Flash subsequent updates via OTA.
94 |
95 | # Documentations
96 | * [Openinverter Web Interface Protocol](PROTOCOL.md)
97 |
98 | # Development
99 | You can choose between the following tools:
100 |
101 | ## Arduino
102 | [Arduino IDE](https://www.arduino.cc/en/software) is an easy-to-use desktop IDE, which provides a quick and integrated way to develop and update your board.
103 | * [Initial setup](doc/ARDUINO_IDE_setup.md)
104 | * [Day to day usage](doc/ARDUINO_IDE_usage.md)
105 |
106 | ## PlatformIO
107 | [PlatformIO](https://platformio.org/) is a set of tools, among which [PlatformIO Core (CLI)](https://docs.platformio.org/en/latest/core/index.html) is a command line interface that can be used to build many kind of projects. In particular Arduino-based projects like this one.
108 | (Note: even if PlatformIO provides an IDE, these instructions only target the CLI.)
109 | * [Initial setup](doc/PLATFORMIO_setup.md)
110 | * [Day to day usage](doc/PLATFORMIO_usage.md)
111 |
--------------------------------------------------------------------------------
/data/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/ajax-loader.gif
--------------------------------------------------------------------------------
/data/chart.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/chart.min.js.gz
--------------------------------------------------------------------------------
/data/chartjs-annotation.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/chartjs-annotation.min.js.gz
--------------------------------------------------------------------------------
/data/docstrings.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | var docstrings = {
22 | data: {
23 | /* spot values */
24 | version: "Firmware version.",
25 | hwver: "Hardware version",
26 | opmode: "Operating mode. 0=Off, 1=Run, 2=Manual_run, 3=Boost, 4=Buck, 5=Sine, 6=2 Phase sine",
27 | lasterr: "Last error message",
28 | status: "",
29 | udc: "Voltage on the DC side of the inverter. a.k.a, battery voltage.",
30 | idc: "Current passing through the DC side of the inverter (calculated).",
31 | il1: "Current passing through the first current sensor on the AC side.",
32 | il2: "Current passing through the second current sensor on the AC side.",
33 | id: "",
34 | iq: "",
35 | ud: "",
36 | uq: "",
37 | heatcur: "",
38 | fstat: "Stator frequency",
39 | speed: "The speed (rpm) of the motor.",
40 | cruisespeed: "",
41 | turns: "Number of turns the motor has completed since startup.",
42 | amp: "Sine amplitude, 37813=max",
43 | angle: "Motor rotor angle, 0-360°. When using the SINE software, the slip is added to the rotor position.",
44 | pot: "Pot value, 4095=max",
45 | pot2: "Regen Pot value, 4095=max",
46 | potnom: "Scaled pot value, 0 accel",
47 | dir: "Rotation direction. -1=REV, 0=Neutral, 1=FWD",
48 | tmphs: "Inverter heatsink temperature",
49 | tmpm: "Motor temperature",
50 | uaux: "Auxiliary voltage (i.e. 12V system). Measured on pin 11 (mprot)",
51 | pwmio: "Raw state of PWM outputs at power up",
52 | canio: "Digital IO bits received via CAN",
53 | din_cruise: "Cruise Control. This pin activates the cruise control with the current speed. Pressing again updates the speed set point.",
54 | din_start: "State of digital input \"start\". This pin starts inverter operation",
55 | din_brake: "State of digital input \"brake\". This pin sets maximum regen torque (brknompedal). Cruise control is disabled.",
56 | din_mprot: "State of digital input \"motor protection switch\". Shuts down the inverter when = 0",
57 | din_forward: "Direction forward.",
58 | din_reverse: "Direction backward.",
59 | din_emcystop: "State of digital input \"emergency stop\". Shuts down the inverter when = 0",
60 | din_ocur: "Over current detected.",
61 | din_desat: "",
62 | din_bms: "BMS over voltage/under voltage.",
63 | cpuload: "CPU load for everything except communication",
64 | /* parameters */
65 | curkp: "Current controller proportional gain",
66 | curki: "Current controller integral gain",
67 | curkifrqgain: "Current controllers integral gain frequency coefficient",
68 | fwkp: "Cross comparison field weakening controller gain",
69 | dmargin: "Margin for residual torque producing current (so field weakening current doesn't use up the entire amplitude)",
70 | syncadv: "Shifts \"syncofs\" downwards/upwards with frequency",
71 | boost: "0 Hz Boost in digit. 1000 digit ~ 2.5%",
72 | fweak: "Frequency where V/Hz reaches its peak",
73 | fconst: "Frequency where slip frequency is derated to form a constant power region. Only has an effect when < fweak",
74 | udcnom: "Nominal voltage for fweak and boost. fweak and boost are scaled to the actual dc voltage. 0=don't scale",
75 | fslipmin: "Slip frequency at minimum throttle",
76 | fslipmax: "Slip frequency at maximum throttle",
77 | fslipconstmax: "Slip frequency at maximum throttle and fconst",
78 | fmin: "Below this frequency no voltage is generated",
79 | polepairs: "Pole pairs of motor (e.g. 4-pole motor: 2 pole pairs)",
80 | respolepairs: "Pole pairs of resolver (normally same as polepairs of motor, but sometimes 1)",
81 | encflt: "Filter constant between pulse encoder and speed calculation. Makes up for slightly uneven pulse distribution",
82 | encmode: "0=single channel encoder, 1=quadrature encoder, 2=quadrature /w index pulse, 3=SPI (deprecated), 4=Resolver, 5=sin/cos chip",
83 | fmax: "At this frequency rev limiting kicks in",
84 | numimp: "Pulse encoder pulses per turn",
85 | dirchrpm: "Motor speed at which direction change is allowed",
86 | dirmode: "0=button (momentary pulse selects forward/reverse), 1=switch (forward or reverse signal must be constantly high)",
87 | syncofs: "Phase shift of sine wave after receiving index pulse",
88 | snsm: "Motor temperature sensor. 12=KTY83, 13=KTY84, 14=Leaf, 15=KTY81",
89 | pwmfrq: "PWM frequency. 0=17.6kHz, 1=8.8kHz, 2=4.4kHz, 3=2.2kHz. Needs PWM restart",
90 | pwmpol: "PWM polarity. 0=active high, 1=active low. DO NOT PLAY WITH THIS! Needs PWM restart",
91 | deadtime: "Deadtime between highside and lowside pulse. 28=800ns, 56=1.5µs. Not always linear, consult STM32 manual. Needs PWM restart",
92 | ocurlim: "Hardware over current limit. RMS-current times sqrt(2) + some slack. Set negative if il1gain and il2gain are negative.",
93 | minpulse: "Narrowest or widest pulse, all other mapped to full off or full on, respectively",
94 | il1gain: "Digits per A of current sensor L1",
95 | il2gain: "Digits per A of current sensor L2",
96 | udcgain: "Digits per V of DC link",
97 | udcofs: "DC link 0V offset",
98 | udclim: "High voltage at which the PWM is shut down",
99 | snshs: "Heatsink temperature sensor. 0=JCurve, 1=Semikron, 2=MBB600, 3=KTY81, 4=PT1000, 5=NTCK45+2k2, 6=Leaf",
100 | pinswap: "Swap pins (only \"FOC\" software). Multiple bits can be set. 1=Swap Current Inputs, 2=Swap Resolver sin/cos, 4=Swap PWM output 1/3\n001 = 1 Swap Currents only\n010 = 2 Swap Resolver only\n011 = 3 Swap Resolver and Currents\n100 = 4 Swap PWM only\n101 = 5 Swap PWM and Currents\n110 = 6 Swap PWM and Resolve\n111 = 7 Swap PWM and Resolver and Currents",
101 | bmslimhigh: "Positive throttle limit on BMS under voltage",
102 | bmslimlow: "Regen limit on BMS over voltage",
103 | udcmin: "Minimum battery voltage",
104 | udcmax: "Maximum battery voltage",
105 | iacmax: "Maximum peak AC current",
106 | idcmax: "Maximum DC input current",
107 | idcmin: "Maximum DC output current (regen)",
108 | throtmax: "Throttle limit",
109 | throtmin: "Throttle regen limit",
110 | ifltrise: "Controls how quickly slip and amplitude recover. The greater the value, the slower",
111 | ifltfall: "Controls how quickly slip and amplitude are reduced on over current. The greater the value, the slower",
112 | chargemode: "0=Off, 3=Boost, 4=Buck",
113 | chargecur: "Charge current setpoint. Boost mode: charger INPUT current. Buck mode: charger output current",
114 | chargekp: "Charge controller gain. Lower if you have oscillation, raise if current set point is not met",
115 | chargeflt: "Charge current filtering. Raise if you have oscillations",
116 | chargemax: "Charge mode duty cycle limit. Especially in boost mode this makes sure you don't overvolt you IGBTs if there is no battery connected.",
117 | potmin: "Value of \"pot\" when pot isn't pressed at all",
118 | potmax: "Value of \"pot\" when pot is pushed all the way in",
119 | pot2min: "Value of \"pot2\" when regen pot is in 0 position",
120 | pot2max: "Value of \"pot2\" when regen pot is in full on position",
121 | potmode: "0=Pot 1 is throttle and pot 2 is regen strength preset. 1=Pot 2 is proportional to pot 1 (redundancy) 2=Throttle controlled via CAN",
122 | throtramp: "Max positive throttle slew rate",
123 | throtramprpm: "No throttle ramping above this speed",
124 | ampmin: "Minimum relative sine amplitude (only \"sine\" software)",
125 | slipstart: "% positive throttle travel at which slip is increased (only \"sine\" software)",
126 | throtcur: "Motor current per % of throttle travel (only \"FOC\" software)",
127 | brknompedal: "Foot on brake pedal regen torque",
128 | brkpedalramp: "Ramp speed when entering regen. E.g. when you set brkmax to 20% and brknompedal to -60% and brkpedalramp to 1, it will take 400ms to arrive at brake force of -60%",
129 | brknom: "Range of throttle pedal travel allocated to regen",
130 | brkmax: "Foot-off throttle regen torque",
131 | brkrampstr: "Below this frequency the regen torque is reduced linearly with the frequency",
132 | brkout: "Activate brake light output at this amount of braking force",
133 | idlespeed: "Motor idle speed. Set to -100 to disable idle function. When idle speed controller is enabled, brake pedal must be pressed on start.",
134 | idlethrotlim: "Throttle limit of idle speed controller",
135 | idlemode: "Motor idle speed mode. 0=always run idle speed controller, 1=only run it when brake pedal is released, 2=like 1 but only when cruise switch is on",
136 | speedkp: "Speed controller gain (Cruise and idle speed). Decrease if speed oscillates. Increase for faster load regulation",
137 | speedflt: "Filter before cruise controller",
138 | cruisemode: "0=button (set when button pressed, reset with brake pedal), 1=switch (set when switched on, reset when switched off or brake pedal)",
139 | udcsw: "Voltage at which the DC contactor is allowed to close",
140 | udcswbuck: "Voltage at which the DC contactor is allowed to close in buck charge mode",
141 | tripmode: "What to do with relays at a shutdown event. 0=All off, 1=Keep DC switch closed, 2=close precharge relay",
142 | pwmfunc: "Quantity that controls the PWM output. 0=tmpm, 1=tmphs, 2=speed",
143 | pwmgain: "Gain of PWM output",
144 | pwmofs: "Offset of PWM output, 4096=full on",
145 | canspeed: "Baud rate of CAN interface 0=250k, 1=500k, 2=800k, 3=1M",
146 | canperiod: "0=send configured CAN messages every 100ms, 1=every 10ms",
147 | fslipspnt: "Slip setpoint in mode 2. Written by software in mode 1",
148 | ampnom: "Nominal amplitude in mode 2. Written by software in mode 1",
149 | },
150 |
151 | get: function(item)
152 | {
153 | if ( item in docstrings.data )
154 | {
155 | return docstrings.data[item];
156 | }
157 | return "";
158 | }
159 |
160 | };
161 |
--------------------------------------------------------------------------------
/data/gauge.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/gauge.min.js.gz
--------------------------------------------------------------------------------
/data/gauges.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
24 |
25 | Huebner Inverter Management Console
26 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/data/gauges.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | var gauges = {};
22 | var items = new Array();
23 |
24 | function onLoad()
25 | {
26 | createGauges();
27 | acquire();
28 | }
29 |
30 | /**
31 | * @brief Creates gauges for all canvases found on the page
32 | */
33 | function createGauges()
34 | {
35 | var div = document.getElementById("gauges");
36 | var paramPart = document.location.href.split("items=");
37 | items = paramPart[1].split(",");
38 |
39 | for (var i = 0; i < items.length; i++)
40 | {
41 | var name = items[i];
42 | var canvas = document.createElement("CANVAS");
43 | canvas.setAttribute("id", name);
44 | div.appendChild(canvas);
45 |
46 | var gauge = new RadialGauge(
47 | {
48 | renderTo: name,
49 | title: name,
50 | width: 300,
51 | height: 300,
52 | minValue: 0,
53 | maxValue: 1,
54 | majorTicks: [0, 1]
55 | });
56 |
57 | gauge.draw();
58 | gauges[name] = gauge;
59 | }
60 | }
61 |
62 | function calcTicks(min, max)
63 | {
64 | var N = 6;
65 | var ticks = [ min ];
66 | var dist = (max - min) / N;
67 | var tick = min;
68 |
69 | for (var i = 0; i < N; i++)
70 | {
71 | tick += dist;
72 | ticks.push(Math.round(tick));
73 | }
74 | return ticks;
75 | }
76 |
77 | function acquire()
78 | {
79 | if (!items.length) return;
80 |
81 | inverter.getValues(items, 1,
82 | function(values)
83 | {
84 | for (var name in values)
85 | {
86 | var val = values[name][0];
87 | gauges[name].options.minValue = Math.min(gauges[name].options.minValue, Math.floor(val * 0.7));
88 | gauges[name].options.maxValue = Math.max(gauges[name].options.maxValue, Math.ceil(val * 1.5));
89 | gauges[name].options.majorTicks = calcTicks(gauges[name].options.minValue, gauges[name].options.maxValue);
90 | gauges[name].value = val;
91 | gauges[name].update();
92 | }
93 | acquire();
94 | });
95 | }
96 |
--------------------------------------------------------------------------------
/data/icon-check-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/icon-check-circle.png
--------------------------------------------------------------------------------
/data/icon-trash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/icon-trash.png
--------------------------------------------------------------------------------
/data/icon-x-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/icon-x-square.png
--------------------------------------------------------------------------------
/data/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
24 | Huebner Inverter Management Console
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ×
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
Communication problem between ESP and STM
63 |
64 |
65 |
66 |
67 |
68 | ×
69 |
New CAN Mapping
70 |
71 |
72 |
Transmit or receive
73 |
74 |
78 |
79 |
Choose transmit to take values/parameters from your OpenInverter board and send them out in frames on the CAN bus where they can be seen by other devices. Choose receive to set values based on data seen on the CAN bus.
80 |
81 |
82 |
Parameter
83 |
84 |
85 |
86 |
Select the spot value/parameter to transmit or receive.
87 |
88 |
89 |
CAN ID
90 |
91 |
Set the ID of the CAN frame to send/receive
92 |
93 |
94 |
Offset
95 |
96 |
Specify the position (offset) within the frame to place (transmit) or find (receive) this value
97 |
98 |
99 |
Length
100 |
101 |
Specify the length of this field in bits
102 |
103 |
104 |
Gain
105 |
106 |
Specify a gain to scale the value up or down
107 |
108 |
109 |
112 |
113 |
114 |
115 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
Actions
184 |
187 |
190 |
191 |
192 |
193 |
194 |
Dashboard
195 |
196 |
197 |
198 |
199 |
200 |
201 |
Inverter messages
202 |
203 |
204 |
205 |
206 |
207 |
208 |
Command
209 |
210 | »
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
Firmware
223 |
224 |
225 |
231 |
232 |
233 |
236 |
237 |
Web Interface
238 |
239 |
240 |
245 |
246 |
247 |
248 |
249 |
Update
250 |
On this page you can apply software updates to your OpenInverter system. There are several distinct pieces of software which go to make up an OpenInverter system and each has a separate update mechanism.
251 |
252 |
OpenInverter Board Bootloader
253 |
Use the stm32_loader.bin file to update the bootloader on your OpenInverter board. You can find download links in the forum here.
254 |
255 |
OpenInverter Board Firmware
256 |
To install a new firmware on your OpenInverter board click on the 'Install firmware from file' button on the right. Use the stm32_sine.bin file to install the sine firmware or use the stm32_foc.bin file to install the FOC firmware. You can find information about the latest available releases, including download links, on the OpenInverter forum here.
257 |
258 |
Web Interface Firmware
259 |
The ESP8266 firmware can be upgraded with platformio via OTA i.e. directly via wifi without programming cable, see forum.
260 | You can also program with a cable, see here for more details.
261 |
262 |
Web Interface Application
263 |
You can apply updates to this web interface by uploading individual files. More information about updating the web interface can be found in its github repository here.
On this page you can configure the CAN mapping settings for your OpenInverter board. CAN mapping allows you to send and receive data via CAN bus. You can specify spot values that you would like to transmit on the CAN bus. Additionally you can specify spot values that you would like to set based on data received on the CAN bus.
488 |
A maximum of 8 items per CAN message can be mapped.
Paid support is also available. See details here.
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
--------------------------------------------------------------------------------
/data/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | var chart;
22 | var items = {};
23 | var stop;
24 | var imgid = 0;
25 | var subscription;
26 |
27 | function sleep(ms) {
28 | return new Promise(resolve => setTimeout(resolve, ms));
29 | }
30 |
31 |
32 |
33 | /** @brief uploads file to web server, Flash using Serial-Wire-Debug. Start address bootloader = 0x08000000, firmware = 0x08001000*/
34 | function uploadSWDFile()
35 | {
36 | var xmlhttp = new XMLHttpRequest();
37 | var form = document.getElementById('swdform');
38 |
39 | if (form.getFormData)
40 | var fd = form.getFormData();
41 | else
42 | var fd = new FormData(form);
43 | var file = document.getElementById('swdfile').files[0];
44 |
45 | xmlhttp.onload = function()
46 | {
47 | var xhr = new XMLHttpRequest();
48 | xhr.seenBytes = 0;
49 | xhr.seenTotalPages = 0;
50 | xhr.onreadystatechange = function() {
51 | if(xhr.readyState == 3) {
52 | var data = xhr.response.substr(xhr.seenBytes);
53 |
54 | if(data.indexOf("Error") != -1) {
55 | document.getElementById("swdbar").style.width = "100%";
56 | document.getElementById("swdbar").innerHTML = "
";
65 |
66 | xhr.seenBytes = xhr.responseText.length;
67 | }
68 | }
69 | };
70 | if (file.name.endsWith('loader.bin'))
71 | {
72 | xhr.open('GET', '/swd/mem/flash?bootloader&file=' + file.name, true);
73 | }else{
74 | xhr.open('GET', '/swd/mem/flash?flash&file=' + file.name, true);
75 | }
76 | xhr.send();
77 | }
78 | xmlhttp.open("POST", "/edit");
79 | xmlhttp.send(fd);
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/data/inverter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 |
22 | /** @brief this is a little cache to store the current params/spot values. This
23 | * is here so that different functions can do look-ups without making a full
24 | * HTTP call to the inverter each time. */
25 |
26 | var paramsCache = {
27 |
28 | data: undefined,
29 | dataById: {},
30 | failedFetchCount: 0,
31 |
32 | get: function(name) {
33 | if ( paramsCache.data !== undefined )
34 | {
35 | if ( name in paramsCache.data ) {
36 | if ( paramsCache.data[name].enums ) {
37 | if (paramsCache.data[name].enums[paramsCache.data[name].value])
38 | {
39 | return paramsCache.data[name].enums[paramsCache.data[name].value];
40 | }
41 | else
42 | {
43 | var active = [];
44 | for (var key in paramsCache.data[name].enums)
45 | {
46 | if (paramsCache.data[name].value & key)
47 | active.push(paramsCache.data[name].enums[key]);
48 | }
49 | return active.join('|');
50 | }
51 | } else {
52 | return paramsCache.data[name].value;
53 | }
54 | }
55 | }
56 | return null;
57 | },
58 |
59 | getEntry: function(name) {
60 | return paramsCache.data[name];
61 | },
62 |
63 | getData: function() { return paramsCache.data; },
64 |
65 | setData: function(data) {
66 | paramsCache.data = data;
67 |
68 | for (var key in data) {
69 | paramsCache.dataById[data[key].id] = data[key];
70 | paramsCache.dataById[data[key].id]['name'] = key;
71 | }
72 | },
73 |
74 | getJson: function() { return JSON.stringify(paramsCache.data); },
75 |
76 | getById: function(id) {
77 | return paramsCache.dataById[id];
78 | }
79 | }
80 |
81 | var inverter = {
82 |
83 | firmwareVersion: 0,
84 |
85 | /** @brief send a command to the inverter */
86 | sendCmd: function(cmd, replyFunc, repeat)
87 | {
88 | var xmlhttp=new XMLHttpRequest();
89 | var req = "/cmd?cmd=" + cmd;
90 |
91 | xmlhttp.onload = function()
92 | {
93 | if (replyFunc) replyFunc(this.responseText);
94 | }
95 |
96 | if (repeat)
97 | req += "&repeat=" + repeat;
98 |
99 | xmlhttp.open("GET", req, true);
100 | xmlhttp.send();
101 | },
102 |
103 | /** @brief get the params from the inverter */
104 | getParamList: function(replyFunc, includeHidden)
105 | {
106 | var cmd = includeHidden ? "json hidden" : "json";
107 |
108 | inverter.sendCmd(cmd, function(reply) {
109 | var params = {};
110 | try
111 | {
112 | params = JSON.parse(reply);
113 |
114 | for (var name in params)
115 | {
116 | var param = params[name];
117 | param.enums = inverter.parseEnum(param.unit);
118 |
119 | if (name == "version")
120 | inverter.firmwareVersion = parseFloat(param.value);
121 | }
122 | paramsCache.failedFetchCount = 0;
123 | }
124 | catch(ex)
125 | {
126 | paramsCache.failedFetchCount += 1;
127 | if ( paramsCache.failedFetchCount >= 2 ){
128 | ui.showCommunicationErrorBar();
129 | }
130 | }
131 | if ( paramsCache.failedFetchCount < 2 )
132 | {
133 | ui.hideCommunicationErrorBar();
134 | }
135 | paramsCache.setData(params);
136 | if (replyFunc) replyFunc(params);
137 | });
138 | },
139 |
140 | getValues: function(items, repeat, replyFunc)
141 | {
142 | var process = function(reply)
143 | {
144 | var expr = /(\-{0,1}[0-9]+\.[0-9]*)/mg;
145 | var signalIdx = 0;
146 | var values = {};
147 |
148 | for (var res = expr.exec(reply); res; res = expr.exec(reply))
149 | {
150 | var val = parseFloat(res[1]);
151 |
152 | if (!values[items[signalIdx]])
153 | values[items[signalIdx]] = new Array()
154 | values[items[signalIdx]].push(val);
155 | signalIdx = (signalIdx + 1) % items.length;
156 | }
157 | replyFunc(values);
158 | };
159 |
160 | if (inverter.firmwareVersion < 3.53 || items.length > 10)
161 | inverter.sendCmd("get " + items.join(','), process, repeat);
162 | else
163 | inverter.sendCmd("stream " + repeat + " " + items.join(','), process);
164 | },
165 |
166 |
167 | /** @brief given the 'unit' string provided by the inverter api, parse out
168 | * the key value pairs and return them in an array.
169 | * @param unit, e.g. "0=None, 1=UdcLow, 2=UdcHigh, 4=UdcBelowUdcSw"
170 | * Example return : ['None', 'UdcLow', 'UdcHigh',,'udcBelowUdcSw']. Note,
171 | * the extra comma is intentional. The position in the array is determined
172 | * by the index on the left hand side of the equals in the 'unit' string.
173 | */
174 | parseEnum: function(unit)
175 | {
176 | var expr = /(\-{0,1}[0-9]+)=([a-zA-Z0-9_\-\.]+)[,\s]{0,2}|([a-zA-Z0-9_\-\.]+)[,\s]{1,2}/g;
177 | var enums = new Array();
178 | var res = expr.exec(unit);
179 |
180 | if (res)
181 | {
182 | do
183 | {
184 | enums[res[1]] = res[2];
185 | } while (res = expr.exec(unit))
186 | //console.log('enums : ' + enums);
187 | return enums;
188 | }
189 | return false;
190 | },
191 |
192 | /** @brief helper function, from a list of parameters send parameter with given index to inverter
193 | * @param params map of parameters (name -> value)
194 | * @param index numerical index which parameter to set */
195 | setParam: function(params, index)
196 | {
197 | var keys = Object.keys(params);
198 |
199 | if (index < keys.length)
200 | {
201 | var key = keys[index];
202 | modal.appendToModal('large', "Setting " + key + " to " + params[key] + " ");
203 | inverter.sendCmd("set " + key + " " + params[key], function(reply) {
204 | modal.appendToModal('large', reply + " ");
205 | // auto-scroll text in modal as it is added
206 | modal.largeModalScrollToBottom();
207 | inverter.setParam(params, index + 1);
208 | });
209 | }
210 | },
211 |
212 | /** @brief Add/Delete a CAN mapping
213 | * @param direction, tx, rx, or del
214 | * @param name, spot value name
215 | * @param id, canid of message
216 | * @param pos, offset within frame
217 | * @param bits, length of field
218 | * @param gain, multiplier
219 | */
220 | canMapping: function(direction, name, id, pos, bits, gain)
221 | {
222 | var cmd = "can " + direction + " " + name + " " + id + " " + pos + " " + bits + " " + gain;
223 | inverter.sendCmd(cmd);
224 | },
225 |
226 | /** @brief get a list of files in the spiffs filesystem on the esp8266 */
227 | getFiles: function(replyFunc)
228 | {
229 | var filesRequest = new XMLHttpRequest();
230 | filesRequest.onload = function()
231 | {
232 | var filesJson = JSON.parse(this.responseText);
233 | replyFunc(filesJson);
234 | }
235 | filesRequest.onerror = function()
236 | {
237 | alert("error");
238 | }
239 | filesRequest.open("GET", "/list", true);
240 | filesRequest.send();
241 | },
242 |
243 | /** @brief delete a file from the spiffs filessytem on the esp8266 */
244 | deleteFile: function(filename, replyFunc)
245 | {
246 | var deleteFileRequest = new XMLHttpRequest();
247 | deleteFileRequest.onload = function()
248 | {
249 | var responseJson = JSON.parse(this.responseText);
250 | replyFunc(responseJson);
251 | }
252 | deleteFileRequest.onerror = function()
253 | {
254 | alert("error");
255 | }
256 | deleteFileRequest.open("DELETE", "/edit?f=" + filename, true);
257 | deleteFileRequest.send();
258 | }
259 |
260 |
261 | };
262 |
--------------------------------------------------------------------------------
/data/jquery.core.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/jquery.core.min.js.gz
--------------------------------------------------------------------------------
/data/jquery.knob.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/jquery.knob.min.js.gz
--------------------------------------------------------------------------------
/data/log.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
24 |
25 | Huebner Inverter Management Console - Datalogger
26 |
27 |
28 |
29 |
30 |
Data Logger
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/data/log.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | var log = {
22 |
23 | items: [],
24 | samples: 0,
25 | textArea: undefined,
26 | minmax: false,
27 | stopLogging: true,
28 |
29 | /* @brief add field to data logger */
30 | addLogItem: function()
31 | {
32 | var dataLoggerConfiguration = document.getElementById('data-logger-configuration');
33 |
34 | // container for the drop down and the delete button
35 | var selectDiv = document.createElement("div");
36 | selectDiv.classList.add('logger-field');
37 | dataLoggerConfiguration.appendChild(selectDiv);
38 |
39 | // Create a drop down and populate it with the possible spot values
40 | var selectSpotValue = document.createElement("select");
41 | selectSpotValue.classList.add('logger-field-select');
42 | for ( var key in paramsCache.getData() )
43 | {
44 | if ( ! paramsCache.getEntry(key).isparam )
45 | {
46 | var option = document.createElement("option");
47 | option.value = key;
48 | option.text = key;
49 | selectSpotValue.appendChild(option);
50 | }
51 | }
52 | selectDiv.appendChild(selectSpotValue);
53 |
54 | // Add the delete button
55 | var deleteButton = document.createElement("button");
56 | var deleteButtonImg = document.createElement('img');
57 | deleteButtonImg.src = '/icon-trash.png';
58 | deleteButton.appendChild(deleteButtonImg);
59 | deleteButton.onclick = function() { this.parentNode.remove(); };
60 | selectDiv.appendChild(deleteButton);
61 | },
62 |
63 | /** @brief return a list of fields currently configured for logger */
64 | getLogItems: function()
65 | {
66 | log.items = [];
67 | var formItems = document.forms["data-logger-configuration"].elements;
68 | for ( var i = 0; i < formItems.length; i++ )
69 | {
70 | if ( formItems[i].type === 'select-one' && formItems[i].classList.contains('logger-field-select') )
71 | {
72 | log.items.push(formItems[i].value);
73 | }
74 | }
75 | },
76 |
77 | /* @brief start collecting log data */
78 | start: function()
79 | {
80 | log.stopLogging = false;
81 | log.getLogItems();
82 | log.textArea = document.getElementById("data-logger-text-area");
83 | log.samples = document.getElementById("data-logger-samples").value;
84 | log.minmax = document.getElementById("data-logger-minmax").checked;
85 | log.textArea.innerHTML = "Timestamp"
86 |
87 | if (log.minmax)
88 | {
89 | for (var i = 0; i < log.items.length; i++)
90 | {
91 | log.textArea.innerHTML += "," + log.items[i] + " (avg)," + log.items[i] + " (min)," + log.items[i] + " (max)";
92 | }
93 | }
94 | else
95 | {
96 | log.textArea.innerHTML += "," + log.items;
97 | }
98 |
99 | log.textArea.innerHTML += "\r\n";
100 | log.acquire(log.samples);
101 | },
102 |
103 | stop: function()
104 | {
105 | log.stopLogging = true;
106 | },
107 |
108 | save: function()
109 | {
110 | var textToWrite = document.getElementById('data-logger-text-area').innerHTML;
111 | var textFileAsBlob = new Blob([ textToWrite ], { type: 'text/csv' });
112 | var fileNameToSaveAs = "log.csv";
113 |
114 | var downloadLink = document.createElement("a");
115 | downloadLink.download = fileNameToSaveAs;
116 | downloadLink.innerHTML = "Download File";
117 | if (window.webkitURL != null)
118 | {
119 | // Chrome allows the link to be clicked without actually adding it to the DOM.
120 | downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
121 | } else {
122 | // Firefox requires the link to be added to the DOM before it can be clicked.
123 | downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
124 | downloadLink.onclick = function(event) {document.body.removeChild(event.target)};
125 | downloadLink.style.display = "none";
126 | document.body.appendChild(downloadLink);
127 | }
128 |
129 | downloadLink.click();
130 | },
131 |
132 | acquire: function(samples)
133 | {
134 | if ( log.stopLogging ){ return; }
135 |
136 | if (!log.items.length) return;
137 |
138 | inverter.getValues(log.items, log.samples,
139 | function(values)
140 | {
141 | var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds
142 | var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0, -1);
143 | var line = localISOTime;
144 | for (var name in values)
145 | {
146 | var avg = values[name].reduce((acc, c) => acc + c, 0) / log.samples;
147 |
148 | if (log.minmax)
149 | {
150 | line += "," + avg.toFixed(2) + "," + Math.min(...values[name]) + "," + Math.max(...values[name]);
151 | }
152 | else
153 | {
154 | line += "," + avg.toFixed(2);
155 | }
156 | }
157 | line += "\r\n";
158 | log.textArea.innerHTML += line;
159 | log.textArea.scrollTop = log.textArea.scrollHeight;
160 | log.acquire(samples);
161 | });
162 | },
163 | }
164 |
--------------------------------------------------------------------------------
/data/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/logo.png
--------------------------------------------------------------------------------
/data/modal.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | var modal = {
22 |
23 | /** @brief Show the large dialog
24 | * @param modal - name of the modal. Valid values : large, small */
25 | showModal: function(modal)
26 | {
27 | var m = document.getElementById(modal + "-modal-overlay");
28 | if ( m !== undefined ){
29 | m.style.display = 'block';
30 | }
31 | else {
32 | console.log("warning, showModal, bad modal choice");
33 | }
34 | },
35 |
36 | /** @brief Hide the modal dialog
37 | * @param modal - name of the modal. Valid values : large, small */
38 | hideModal: function(modal)
39 | {
40 | var m = document.getElementById(modal + "-modal-overlay");
41 | if ( m !== undefined ){
42 | m.style.display = 'none';
43 | }
44 | else {
45 | console.log("warning, hideModal, bad modal choice");
46 | }
47 | },
48 |
49 | /** @brief Set the header text on the modal
50 | * @param modal - name of the modal. Valid values : large, small
51 | * @param headerText - string to place into the header field of the modal */
52 | setModalHeader: function(modal, headerText)
53 | {
54 | var m = document.getElementById(modal + "-modal-header");
55 | if ( m !== undefined ){
56 | m.innerHTML = headerText;
57 | }
58 | else {
59 | console.log("warning, setModalHeader, bad modal choice");
60 | }
61 | },
62 |
63 | /** @brief Empty the contents of the large modal
64 | * @param modal - name of the modal. Valid values : large, small */
65 | emptyModal: function(modal)
66 | {
67 | var m = document.getElementById(modal + "-modal-content");
68 | if ( m !== undefined ){
69 | m.innerHTML = "";
70 | }
71 | else {
72 | console.log("warning, emptyModal, bad modal choice");
73 | }
74 | },
75 |
76 | /** @brief Append content to large modal
77 | * @param modal - name of the modal. Valid values : large, small
78 | * @appendText - string to append to the body of the modal. Note, can be html. */
79 | appendToModal: function(modal, appendText)
80 | {
81 | var modalId = modal + "-modal-content";
82 | //console.log("appendToModal : looking for div " + modalId);
83 | var modalContent = document.getElementById(modalId);
84 | if ( modalContent !== undefined ){
85 | modalContent.innerHTML += appendText;
86 | }
87 | else {
88 | console.log("warning, appendToModal, bad modal choice");
89 | }
90 | },
91 |
92 | //* @brief auto-scroll large modal content */
93 | largeModalScrollToBottom(){
94 | var largeModalContent = document.getElementById("large-modal-content");
95 | largeModalContent.scrollTop = largeModalContent.scrollHeight;
96 | },
97 |
98 | };
--------------------------------------------------------------------------------
/data/plot.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of the esp8266 web interface
3 | *
4 | * Copyright (C) 2018 Johannes Huebner
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | *
19 | */
20 |
21 | var plot = {
22 |
23 | stop: true,
24 |
25 | /** @brief launch new tab showing gauges based fields selected from the plot&gauge page */
26 | launchGauges: function()
27 | {
28 | var items = ui.getPlotItems();
29 | var req = "gauges.html?items=" + items.names.join(',')
30 | window.open(req);
31 | },
32 |
33 | /** @brief generates chart at bottom of page */
34 | generateChart: function()
35 | {
36 | chart = new Chart("canvas", {
37 | type: "line",
38 | options: {
39 | animation: {
40 | duration: 0
41 | },
42 | scales: {
43 | yAxes: [{
44 | type: "linear",
45 | display: true,
46 | position: "left",
47 | id: "left"
48 | }, {
49 | type: "linear",
50 | display: true,
51 | position: "right",
52 | id: "right",
53 | gridLines: { drawOnChartArea: false }
54 | }]
55 | }
56 | } });
57 | },
58 |
59 | /** @brief start plotting selected spot values */
60 | startPlot: function()
61 | {
62 | items = ui.getPlotItems();
63 | var colours = [ 'rgb(255, 99, 132)', 'rgb(54, 162, 235)', 'rgb(255, 159, 64)', 'rgb(153, 102, 255)', 'rgb(255, 205, 86)', 'rgb(75, 192, 192)' ];
64 |
65 | chart.config.data.datasets = new Array();
66 |
67 | for (var signalIdx = 0; signalIdx < items.names.length; signalIdx++)
68 | {
69 | var newDataset = {
70 | label: items.names[signalIdx],
71 | data: [],
72 | borderColor: colours[signalIdx % colours.length],
73 | backgroundColor: colours[signalIdx % colours.length],
74 | fill: false,
75 | pointRadius: 0,
76 | yAxisID: items.axes[signalIdx]
77 | };
78 | chart.config.data.datasets.push(newDataset);
79 | }
80 |
81 | ui.setAutoReload(false);
82 | time = 0;
83 | chart.update();
84 | plot.stop = false;
85 | document.getElementById("pauseButton").disabled = false;
86 | plot.acquire();
87 | },
88 |
89 | /** @brief Stop plotting */
90 | stopPlot: function()
91 | {
92 | plot.stop = true;
93 | document.getElementById("pauseButton").disabled = false;
94 | ui.setAutoReload(true);
95 | },
96 |
97 | /** @brief pause or resume plotting */
98 | pauseResumePlot: function()
99 | {
100 | if (plot.stop)
101 | {
102 | plot.stop = false;
103 | plot.acquire();
104 | }
105 | else
106 | {
107 | plot.stop = true;
108 | }
109 | },
110 |
111 | acquire: function()
112 | {
113 | if (plot.stop) return;
114 | if (!items.names.length) return;
115 | var burstLength = document.getElementById('burstLength').value;
116 | var maxValues = document.getElementById('maxValues').value;
117 |
118 | inverter.getValues(items.names, burstLength,
119 | function(values)
120 | {
121 | for (var i = 0; i < burstLength; i++)
122 | {
123 | chart.config.data.labels.push(time);
124 | time++;
125 | }
126 | chart.config.data.labels.splice(0, Math.max(chart.config.data.labels.length - maxValues, 0));
127 |
128 | for (var name in values)
129 | {
130 | var data = chart.config.data.datasets.find(function(element) { return element.label == name }).data;
131 |
132 | for (var i = 0; i < values[name].length; i++)
133 | {
134 | data.push(values[name][i])
135 | data.splice(0, Math.max(data.length - maxValues, 0));
136 | }
137 | }
138 |
139 | chart.update();
140 | plot.acquire();
141 | });
142 | },
143 |
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/data/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp32-web-interface/869b5de1d2baea3b35d26e6f96e832565c9f6236/data/refresh.png
--------------------------------------------------------------------------------
/data/remote.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | openinverter.org remote support module
4 |
5 |
6 |
7 |
8 |
Remote Support Module
9 | Welcome to remote support! In order for this module to work, this page must be opened on
10 | a device that is connected to both the inverter AND the internet. There are multiple ways
11 | to achieve this. Pick the one that suits you best.
12 |
13 |
Open it on a smart phone that is connected to the internet via mobile data and to the inverter via wifi. Make sure your phone never enters standby mode during the support session.
14 |
Open it on a regular PC/laptop that is connected to the internet via Ethernet and connected to the inverter via wifi
15 |
Make the inverter wifi module connect to your local hotspot and access this wifi interface via the hotpot assigned IP address
16 |
17 | Below you can see which commands are issued by remote support.
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/data/rtc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
24 |
25 | RTC Settings
26 |
27 |
94 |
102 |
103 |
104 |