├── .gitignore
├── Makefile
├── PROTOCOL.md
├── README.md
├── TODO.md
├── data
├── ajax-loader.gif
├── chart.min.js.gz
├── chartjs-annotation.min.js.gz
├── distribution.files.lst
├── distribution.files.lst.orig
├── 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
├── 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
├── esp8266-web-interface.ino
├── platformio-local-override.ini.example
├── platformio.ini
├── src
├── LICENSE
├── arm_debug.cpp
├── arm_debug.h
├── arm_reg.h
└── flashloader
│ ├── LICENSE
│ ├── Makefile
│ ├── linker.ld
│ ├── stm32f0.h
│ ├── stm32f0.s
│ ├── stm32x.h
│ └── stm32x.s
├── svg
├── activity.svg
├── check-circle.svg
├── cpu.svg
├── database.svg
├── download.svg
├── file-text.svg
├── grid.svg
├── help-circle.svg
├── pause.svg
├── play.svg
├── plus-circle.svg
├── repeat.svg
├── rotate-ccw.svg
├── save.svg
├── slider.svg
├── square.svg
├── target.svg
├── terminal.svg
├── trash-2.svg
├── trash.svg
├── upload-cloud.svg
└── wifi.svg
└── upload.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .pio/
3 | platformio-local-override.ini
4 | tmp
5 |
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | cwd := $(shell pwd)
3 | DISTFILES := $(shell cat OpenInverterWeb/data/distribution.files.lst)
4 |
5 | all: install
6 |
7 | install:
8 | @for distfile in $(shell cat OpenInverterWeb/data/distribution.files.lst); do \
9 | echo $${distfile} ; \
10 | curl -F "data=@$$distfile" http://${INVERTER_IP}/edit ; \
11 | done
12 |
--------------------------------------------------------------------------------
/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 failure 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 mode. Mode 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 | ```
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | esp8266-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 ESP8266 boards, such as:
54 | * [Olimex MOD-WIFI-ESP8266](https://www.olimex.com/Products/IoT/ESP8266/MOD-WIFI-ESP8266/open-source-hardware)
55 |
56 | (Pay attention to the SPI flash chip on your board: some need a special mode `QOUT` instead of `QIO` for programming)
57 |
58 | You can buy pre-programmed boards:
59 | * [OpenInverter shop](https://openinverter.org/shop/index.php?route=product/product&path=59&product_id=56)
60 | * [EVBMW Shop](https://www.evbmw.com/index.php/evbmw-webshop/vcu-boards/wifi-progged) (Note : you can choose between OpenInverter or Lexus VCU firmware version )
61 |
62 | # Firmware
63 | You can find pre-compiled versions of the firmware on the [OpenInverter forum](https://openinverter.org/forum), or can compile it yourself
64 | by following the [instructions below](#development).
65 |
66 | # Flashing / Upgrading
67 | ## Wirelessly
68 | If your board is already programmed with this esp8266-web-interface firmware, or with a firmware that has either ESP8266HTTPUpdateServer or ArduinoOTA components compiled in (it may be the case with the default firmware when you buy a new module), and if you can already reach it (WiFi + network); then you can use one of these approachs:
69 |
70 | * Using ESP8266HTTPUpdateServer component
71 | * Either go to http://inverter.local/update and upload the binary firmware file
72 | * Or use this command line `curl -F "image=@firmware.bin" inverter.local/update`
73 | * Using the ArduinoOTA component
74 | * Use the `espota.py` tool (available in the Arduino tools) to upload either a binary firmware file, or a binary filesystem file:
75 | * Firmware: `python ..../Arduino15/packages/esp8266/hardware/esp8266/3.0.2/tools/espota.py -i esp8266-761bb8.local --progress --file firmware.bin`
76 | * Filesystem: `python ..../Arduino15/packages/esp8266/hardware/esp8266/3.0.2/tools/espota.py -i esp8266-761bb8.local --spiffs --progress --file spiffs.bin`
77 | * Using your development environment (see the [instructions below](#development))
78 |
79 | ## Wired
80 | 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.
81 | Assuming you're using the original Olimex board, you'll need :
82 | * A 3.3v capable USB / Serial adapter
83 | * the following connections:
84 |
85 | Pin# | ESP8266 Board Function | USB / Serial adapter
86 | ----- | ---------------------- | --------------------
87 | 1 | +3.3v input | (Some adapters provide a +3.3v output, you can use it)
88 | 2 | GND | GND
89 | 3 | RXD input | TXD output
90 | 4 | TXD output | RXD input
91 |
92 | 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.
93 |
94 | # Documentations
95 | * [Openinverter Web Interface Protocol](PROTOCOL.md)
96 |
97 | # Development
98 | You can choose between the following tools:
99 |
100 | ## Arduino
101 | [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.
102 | * [Initial setup](doc/ARDUINO_IDE_setup.md)
103 | * [Day to day usage](doc/ARDUINO_IDE_usage.md)
104 |
105 | ## PlatformIO
106 | [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.
107 | (Note: even if PlatformIO provides an IDE, these instructions only target the CLI.)
108 | * [Initial setup](doc/PLATFORMIO_setup.md)
109 | * [Day to day usage](doc/PLATFORMIO_usage.md)
110 |
111 | # OpenInverter esp8266 Web Interface (Huebner Inverter)
112 |
113 | This repository contains the software which runs on the esp8266 WiFi modules
114 | used as part of the OpenInverter project. Its purpose is to provide a web
115 | interface for the configuration and monitoring of an OpenInverter based system.
116 |
117 | There are two parts to the esp8266 WiFi module software - a firmware and a web
118 | application. The firmware implements a HTTP API and a small web server. The HTTP
119 | API sits on top of a serial communication protocol between the esp8266 and the
120 | OpenInverter board. The web server hosts the HTML/css/js files which make up the
121 | end user web application.
122 |
123 | ## Connecting an esp8266 to your laptop/desktop for programming
124 |
125 | You can use an FTDI board to connect your esp8266 module to your laptop/desktop
126 | in order to program it. Only four cables are required between the FTDI and the
127 | esp8266 - 3.3V, GND, RX,and TX.
128 |
129 | TX on the esp8266 connects to RX on the FTDI and vice versa.
130 |
131 | Connect the USB port of the FTDI to your laptop/desktop.
132 |
133 | You can find the pinout of the Olimex esp8266 module here: https://github.com/OLIMEX/ESP8266/
134 |
135 | Warning: be sure your FTDI board is set to 3.3v and not 5v mode. If it is in 5v
136 | mode, it may damage your esp8266 module.
137 |
138 |
139 | ## Setting up your laptop/desktop to program an esp8266 board
140 |
141 | 1. Install the Arduino application
142 | 2. Add esp8266 board support in the Arduino application. See
143 | https://github.com/esp8266/Arduino for more details on how to do this.
144 |
145 |
146 |
147 | ## Installing this software on an esp8266
148 |
149 | 1. Fetch the code in this repository.
150 |
151 | Download the zip file at this link:
152 | https://github.com/jsphuebner/esp8266-web-interface/archive/refs/heads/master.zip
153 |
154 | ~~ OR ~~
155 |
156 | Clone the code with git.
157 |
158 | ```
159 | git clone https://github.com/jsphuebner/esp8266-web-interface.git
160 | ```
161 |
162 | 2. Open OpenInverterWeb/OpenInverterWeb.ino in the Arduino application.
163 |
164 | 3. Ensure you have the correct settings in Arduino for the esp8266.
165 |
166 | - Tools > Board > "Olimex MOD-WIFI-ESP8266(-DEV)""
167 | - Tools > Upload speed > 115200
168 | - Tools > Flash Size > 2MB
169 | - Tools > Debug Port > Disabled
170 | - Tools > Port > Whatever serial port your esp8266 appears on
171 |
172 | 4. Install the firmware.
173 |
174 | Sketch > upload.
175 |
176 | 5. Install the HTML/CSS/js files on the esp8266 which make up the web interface.
177 |
178 | Tools > ESP8266 Sketch Data Upload
179 |
180 |
181 | ## Makefile-based installation
182 |
183 | If you already have a copy of the firmware/web interface installed and just want
184 | to update the web interface to the latest version, you can use the makefile
185 | included here. This can be useful when developing against the esp8266 for
186 | iterating changes quickly.
187 |
188 | 1. Install the 'make' tool on your PC. E.g. sudo apt-get install build-essential
189 | Debian/Ubuntu.
190 |
191 | 2. Set the INVERTER_IP environment variable to the IP of your esp8266. If you
192 | are connecting your PC directly to the esp8266, the IP will be 192.168.4.1
193 | usually.
194 |
195 | ```
196 | export INVERTER_IP=192.168.4.1
197 | ```
198 |
199 | 3. Upload the web interface files
200 |
201 | ```
202 | make install
203 | ```
204 |
205 |
206 | ## Related links
207 | * https://github.com/esp8266/Arduino
208 | * https://arduino-esp8266.readthedocs.io/en/latest/index.html
209 |
210 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # To Do List
2 | - [x] Context-specific help?
3 | - [x] Hitting enter when focus is on the command box should send the command (currently have to click the button)
4 | - [x] pin table header on spot values and parameters pages
5 | - [x] Separate 'can mapping' page
6 | - [x] Move spot value selection logic from spot values page to plot page
7 | - [ ] Documentation on how to use the development environment
8 | - [x] WiFi Settings page
9 | - [x] Fix data logger page
10 | - [x] Make dashboard content active and automatically updated
11 | - [ ] /wifi mocks?
12 | - [x] Support page
13 | - [ ] Make it all work on a small screen (menu could collapse to just icons and/or popout on mouseover)
14 | - [x] Show version string at bottom of menu
15 | - [ ] Visual feedback that changes have been accepted when modifying params
16 | - [x] Visual feedback that parameters have been saved
17 | - [ ] Auto updates for web interface
18 | - [x] Experimental features toggle
19 | - [x] Hide the SWD form by default
20 | - [x] Erase flash confirm dialog
21 | - [x] Hard reset confirm dialog
22 | - [x] Freeze params when selected
23 | - [x] Restore params from flash confirm
24 | - [ ] Submit params to openinverter
25 | - [ ] Subscribe to parameter set finish
26 |
27 | - [ ] Download flash - doesn't work, even on old UI
28 | - [ ] Download bootloader - doesn't work, even on old UI
--------------------------------------------------------------------------------
/data/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/ajax-loader.gif
--------------------------------------------------------------------------------
/data/chart.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/chart.min.js.gz
--------------------------------------------------------------------------------
/data/chartjs-annotation.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/chartjs-annotation.min.js.gz
--------------------------------------------------------------------------------
/data/distribution.files.lst:
--------------------------------------------------------------------------------
1 | index.html
2 | index.js
3 | inverter.js
4 | style.css
5 | log.js
6 | wifi.js
7 | plot.js
8 | icon-eye.png
9 | icon-tool.png
10 |
--------------------------------------------------------------------------------
/data/distribution.files.lst.orig:
--------------------------------------------------------------------------------
1 | icon-sliders.png
2 | icon-grid.png
3 | ajax-loader.gif
4 | chart.min.js
5 | chartjs-annotation.min.js.gz
6 | gauge.min.js
7 | gauges.html
8 | gauges.js
9 | icon-activity.png
10 | icon-check-circle.png
11 | icon-cpu.png
12 | icon-database.png
13 | icon-download.png
14 | icon-file-text.png
15 | icon-grid.png
16 | icon-help-circle.png
17 | icon-pause.png
18 | icon-play.png
19 | icon-plus-circle.png
20 | icon-repeat.png
21 | icon-rotate-ccw.png
22 | icon-save.png
23 | icon-sliders.png
24 | icon-square.png
25 | icon-target.png
26 | icon-terminal.png
27 | icon-trash-2.png
28 | icon-trash.png
29 | icon-upload-cloud.png
30 | icon-wifi.png
31 | index.html
32 | index.js
33 | inverter.js
34 | jquery.core.min.js.gz
35 | jquery.knob.min.js.gz
36 | log.html
37 | log.js
38 | logo.png
39 | refresh.png
40 | remote.html
41 | style.css
42 | syncofs.html
43 | wifi-updated.html
44 | wifi.html
45 | icon-upload.png
46 | icon-zap.png
47 | wifi.js
48 | plot.js
49 | icon-clock.png
50 | icon-download-cloud.png
51 | icon-tool.png
52 |
--------------------------------------------------------------------------------
/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/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/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/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/icon-check-circle.png
--------------------------------------------------------------------------------
/data/icon-trash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/icon-trash.png
--------------------------------------------------------------------------------
/data/icon-x-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/icon-x-square.png
--------------------------------------------------------------------------------
/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 | return paramsCache.data[name].enums[paramsCache.data[name].value];
38 | } else {
39 | return paramsCache.data[name].value;
40 | }
41 | }
42 | }
43 | return null;
44 | },
45 |
46 | getEntry: function(name) {
47 | return paramsCache.data[name];
48 | },
49 |
50 | getData: function() { return paramsCache.data; },
51 |
52 | setData: function(data) {
53 | paramsCache.data = data;
54 |
55 | for (var key in data) {
56 | paramsCache.dataById[data[key].id] = data[key];
57 | paramsCache.dataById[data[key].id]['name'] = key;
58 | }
59 | },
60 |
61 | getJson: function() { return JSON.stringify(paramsCache.data); },
62 |
63 | getById: function(id) {
64 | return paramsCache.dataById[id];
65 | }
66 | }
67 |
68 | var inverter = {
69 |
70 | firmwareVersion: 0,
71 |
72 | /** @brief send a command to the inverter */
73 | sendCmd: function(cmd, replyFunc, repeat)
74 | {
75 | var xmlhttp=new XMLHttpRequest();
76 | var req = "/cmd?cmd=" + cmd;
77 |
78 | xmlhttp.onload = function()
79 | {
80 | if (replyFunc) replyFunc(this.responseText);
81 | }
82 |
83 | if (repeat)
84 | req += "&repeat=" + repeat;
85 |
86 | xmlhttp.open("GET", req, true);
87 | xmlhttp.send();
88 | },
89 |
90 | /** @brief get the params from the inverter */
91 | getParamList: function(replyFunc, includeHidden)
92 | {
93 | var cmd = includeHidden ? "json hidden" : "json";
94 |
95 | inverter.sendCmd(cmd, function(reply) {
96 | var params = {};
97 | try
98 | {
99 | params = JSON.parse(reply);
100 |
101 | for (var name in params)
102 | {
103 | var param = params[name];
104 | param.enums = inverter.parseEnum(param.unit);
105 |
106 | if (name == "version")
107 | inverter.firmwareVersion = parseFloat(param.value);
108 | }
109 | paramsCache.failedFetchCount = 0;
110 | }
111 | catch(ex)
112 | {
113 | paramsCache.failedFetchCount += 1;
114 | if ( paramsCache.failedFetchCount >= 2 ){
115 | ui.showCommunicationErrorBar();
116 | }
117 | }
118 | if ( paramsCache.failedFetchCount < 2 )
119 | {
120 | ui.hideCommunicationErrorBar();
121 | }
122 | paramsCache.setData(params);
123 | if (replyFunc) replyFunc(params);
124 | });
125 | },
126 |
127 | getValues: function(items, repeat, replyFunc)
128 | {
129 | var process = function(reply)
130 | {
131 | var expr = /(\-{0,1}[0-9]+\.[0-9]*)/mg;
132 | var signalIdx = 0;
133 | var values = {};
134 |
135 | for (var res = expr.exec(reply); res; res = expr.exec(reply))
136 | {
137 | var val = parseFloat(res[1]);
138 |
139 | if (!values[items[signalIdx]])
140 | values[items[signalIdx]] = new Array()
141 | values[items[signalIdx]].push(val);
142 | signalIdx = (signalIdx + 1) % items.length;
143 | }
144 | replyFunc(values);
145 | };
146 |
147 | if (inverter.firmwareVersion < 3.53 || items.length > 10)
148 | inverter.sendCmd("get " + items.join(','), process, repeat);
149 | else
150 | inverter.sendCmd("stream " + repeat + " " + items.join(','), process);
151 | },
152 |
153 |
154 | /** @brief given the 'unit' string provided by the inverter api, parse out
155 | * the key value pairs and return them in an array.
156 | * @param unit, e.g. "0=None, 1=UdcLow, 2=UdcHigh, 4=UdcBelowUdcSw"
157 | * Example return : ['None', 'UdcLow', 'UdcHigh',,'udcBelowUdcSw']. Note,
158 | * the extra comma is intentional. The position in the array is determined
159 | * by the index on the left hand side of the equals in the 'unit' string.
160 | */
161 | parseEnum: function(unit)
162 | {
163 | var expr = /(\-{0,1}[0-9\.]+)=([a-zA-Z0-9_\-\.]+)[,\s]{0,2}|([a-zA-Z0-9_\-\.]+)[,\s]{1,2}/g;
164 | var enums = {};
165 | var res = expr.exec(unit);
166 |
167 | if (res)
168 | {
169 | do
170 | {
171 | enums[res[1]] = res[2];
172 | } while (res = expr.exec(unit))
173 | //console.log('enums : ' + enums);
174 | return enums;
175 | }
176 | return false;
177 | },
178 |
179 | /** @brief helper function, from a list of parameters send parameter with given index to inverter
180 | * @param params map of parameters (name -> value)
181 | * @param index numerical index which parameter to set */
182 | setParam: function(params, index)
183 | {
184 | var keys = Object.keys(params);
185 |
186 | if (index < keys.length)
187 | {
188 | var key = keys[index];
189 | modal.appendToModal('large', "Setting " + key + " to " + params[key] + " ");
190 | inverter.sendCmd("set " + key + " " + params[key], function(reply) {
191 | modal.appendToModal('large', reply + " ");
192 | // auto-scroll text in modal as it is added
193 | modal.largeModalScrollToBottom();
194 | inverter.setParam(params, index + 1);
195 | });
196 | }
197 | },
198 |
199 | /** @brief Add/Delete a CAN mapping
200 | * @param direction, tx, rx, or del
201 | * @param name, spot value name
202 | * @param id, canid of message
203 | * @param pos, offset within frame
204 | * @param bits, length of field
205 | * @param gain, multiplier
206 | */
207 | canMapping: function(direction, name, id, pos, bits, gain)
208 | {
209 | var cmd = "can " + direction + " " + name + " " + id + " " + pos + " " + bits + " " + gain;
210 | inverter.sendCmd(cmd);
211 | },
212 |
213 | /** @brief get a list of files in the spiffs filesystem on the esp8266 */
214 | getFiles: function(replyFunc)
215 | {
216 | var filesRequest = new XMLHttpRequest();
217 | filesRequest.onload = function()
218 | {
219 | var filesJson = JSON.parse(this.responseText);
220 | replyFunc(filesJson);
221 | }
222 | filesRequest.onerror = function()
223 | {
224 | alert("error");
225 | }
226 | filesRequest.open("GET", "/list", true);
227 | filesRequest.send();
228 | },
229 |
230 | /** @brief delete a file from the spiffs filessytem on the esp8266 */
231 | deleteFile: function(filename, replyFunc)
232 | {
233 | var deleteFileRequest = new XMLHttpRequest();
234 | deleteFileRequest.onload = function()
235 | {
236 | var responseJson = JSON.parse(this.responseText);
237 | replyFunc(responseJson);
238 | }
239 | deleteFileRequest.onerror = function()
240 | {
241 | alert("error");
242 | }
243 | deleteFileRequest.open("DELETE", "/edit?f=" + filename, true);
244 | deleteFileRequest.send();
245 | }
246 |
247 |
248 | };
249 |
--------------------------------------------------------------------------------
/data/jquery.core.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/jquery.core.min.js.gz
--------------------------------------------------------------------------------
/data/jquery.knob.min.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/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/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/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/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/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 |