diff --git a/CHANGES.rst b/CHANGES.rst
new file mode 100644
index 0000000..c6096a8
--- /dev/null
+++ b/CHANGES.rst
@@ -0,0 +1,27 @@
+#####################
+ISEMS-ESP32 changelog
+#####################
+
+
+Development
+===========
+- Add dry-dock environment.
+- Add MQTT- and HTTP-based telemetry with JSON encoding.
+
+
+2020-01-09 0.0.0
+================
+- Add the full set of LUA files to run the new hardware board.
+- Fix temperature sensor detection and battery charge state estimatation bugs.
+- Cleanup debugging statements.
+
+
+2019-12-03 0.0.0
+================
+- Add preliminary version of ISEMS-ESP32 for external module.
+
+
+2019-10-07 0.0.0
+================
+- Initial commit.
+- Add README.
diff --git a/LUA/ff-esp32-openmppt/config.lua b/LUA/ff-esp32-openmppt/config.lua
index 712925f..6cc17ee 100644
--- a/LUA/ff-esp32-openmppt/config.lua
+++ b/LUA/ff-esp32-openmppt/config.lua
@@ -10,6 +10,9 @@ long = 13.404954
-- Node-ID
nodeid="ESP32-Meshnode-1"
+-- The telemetry channel to send metrics to.
+-- See also MQTT and HTTP configuration below.
+telemetry_channel = "isems/testdrive/foobar/" .. nodeid
-- Rated capacity of battery in Ampere hours (Ah)
rated_batt_capacity = 8.0
@@ -54,3 +57,12 @@ sta_ssid="AP2.freifunk.net"
-- WPA key to connect to the existing AP as WiFi client
sta_pwd=""
+
+
+-- Telemetry configuration for MQTT and HTTP
+mqtt_enabled = false
+mqtt_broker = "isems.mqtthub.net"
+mqtt_topic = telemetry_channel
+
+http_enabled = false
+http_endpoint = "http://isems.mqtthub.net/api-notls/" .. telemetry_channel .. "/data.json"
diff --git a/LUA/ff-esp32-openmppt/mp2.lua b/LUA/ff-esp32-openmppt/mp2.lua
index 837967a..9d359f3 100644
--- a/LUA/ff-esp32-openmppt/mp2.lua
+++ b/LUA/ff-esp32-openmppt/mp2.lua
@@ -501,7 +501,7 @@ statuscode = (bin2hextable[bit_string_0] .. bin2hextable[bit_string_1] .. bin2he
print("statuscode =", statuscode)
--- Create CSV data set
+-- CSV payload
timestamp = time.get()
@@ -511,7 +511,7 @@ print(nodeid, packetrev, timestamp, firmware_type, nextreboot, powersave, V_oc,
ffopenmppt_log = nodeid .. ";" .. packetrev .. ";" .. timestamp .. ";" .. firmware_type .. ";" .. nextreboot .. ";" .. powersave .. ";".. V_oc .. ";".. V_in .. ";".. V_out .. ";".. charge_state_int .. ";" .. health_estimate .. ";".. battery_temperature .. ";".. low_voltage_disconnect .. ";".. V_out_max_temp .. ";" .. rated_batt_capacity .. ";".. solar_module_capacity .. ";".. lat .. ";" .. long .. ";" .. statuscode
-print(ffopenmppt_log)
+print("CSV payload:", ffopenmppt_log)
if ffopenmppt_log5 ~= nil then
ffopenmppt_log1 = ffopenmppt_log2
@@ -531,8 +531,12 @@ if ffopenmppt_log5 ~= nil then
elseif ffopenmppt_log1 == nil then ffopenmppt_log1 = ffopenmppt_log csvlog = ffopenmppt_log1
- end
-
+end
+
+-- HTTP- and MQTT telemetry
+dofile "telemetry.lua"
+
+-- HTML output
pagestring = "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n
Independent Solar Energy Mesh
Status of " .. nodeid
pagestring = pagestring .. " (local node)
Summary: " .. charge_status .. ". " .. system_status
pagestring = pagestring .. "
Charge state: "
diff --git a/LUA/ff-esp32-openmppt/telemetry.lua b/LUA/ff-esp32-openmppt/telemetry.lua
new file mode 100644
index 0000000..d7608be
--- /dev/null
+++ b/LUA/ff-esp32-openmppt/telemetry.lua
@@ -0,0 +1,114 @@
+--[[
+Telemetry implementation for MQTT and HTTP.
+]]
+
+function get_telemetry_data()
+ --[[
+ Collect all metric values from global variables
+ and bundle them into a single telemetry data container.
+
+ Note: This might well be improved but for now it's better than nothing.
+ ]]
+ data = {
+ nodeId = nodeid,
+ isemsRevision = packetrev,
+ timestamp = timestamp,
+ timeToShutdown = nextreboot,
+ isPowerSaveMode = powersave,
+ openCircuitVoltage = V_oc,
+ mppVoltage = V_in,
+ batteryVoltage = V_out,
+ batteryChargeEstimate = charge_state_int,
+ batteryHealthEstimate = health_estimate,
+ batteryTemperature = battery_temperature,
+ lowVoltageDisconnectVoltage = low_voltage_disconnect,
+ temperatureCorrectedVoltage = V_out_max_temp,
+ rateBatteryCapacity = rated_batt_capacity,
+ ratedSolarModuleCapacity = solar_module_capacity,
+ latitude = lat,
+ longitude = long,
+ status = statuscode,
+ }
+ return data
+end
+
+function mqtt_publish(data)
+ --[[
+ MQTT telemetry
+
+ Encode telemetry data as JSON and publish message to
+ MQTT broker at topic configured within "config.lua".
+ ]]
+
+ print("Submitting telemetry data to MQTT broker.")
+
+ -- JSON payload
+ -- https://nodemcu.readthedocs.io/en/master/modules/sjson/
+ -- https://github.com/ISEMS/isems-data-collector/blob/926eb4a3/test_importer.py
+ print("Creating JSON payload.")
+ sjson.encode(data)
+ ok, json = pcall(sjson.encode, data)
+ if ok then
+ print("JSON payload:", json)
+ else
+ print("ERROR: Encoding to JSON failed!")
+ return
+ end
+
+ -- https://nodemcu.readthedocs.io/en/master/modules/mqtt/
+ m = mqtt.Client("isems-" .. nodeid, 120)
+ m:connect(mqtt_broker, 1883, 0,
+ function(client)
+ print("Connected to MQTT broker.")
+ client:publish(mqtt_topic, "hello", 0, 0, function(client) print("MQTT message sent.") end)
+ end,
+ function(client, reason)
+ print("MQTT connect failed. Reason: " .. reason)
+ end
+ )
+
+end
+
+function http_post(data)
+ --[[
+ HTTP telemetry
+
+ Encode telemetry data as JSON and send as POST request
+ to HTTP endpoint configured within "config.lua".
+ ]]
+
+ print("Submitting telemetry data to HTTP endpoint.")
+
+ -- JSON payload
+ -- https://nodemcu.readthedocs.io/en/master/modules/sjson/
+ -- https://github.com/ISEMS/isems-data-collector/blob/926eb4a3/test_importer.py
+ print("Creating JSON payload.")
+ ok, json = pcall(sjson.encode, data)
+ if ok then
+ print("JSON payload:", json)
+ else
+ print("ERROR: Encoding to JSON failed!")
+ return
+ end
+
+ -- https://nodemcu.readthedocs.io/en/master/modules/http/
+ http.post(http_endpoint,
+ 'Content-Type: application/json\r\n',
+ json,
+ function(code, data)
+ if (code < 0) then
+ print("HTTP request failed", code, data)
+ else
+ print("HTTP request succeeded", code, data)
+ end
+ end)
+
+end
+
+if mqtt_enabled == true then
+ mqtt_publish(get_telemetry_data())
+end
+
+if http_enabled == true then
+ http_post(get_telemetry_data())
+end
diff --git a/LUA/ff-esp32-openmppt/test/README.rst b/LUA/ff-esp32-openmppt/test/README.rst
new file mode 100644
index 0000000..44fed49
--- /dev/null
+++ b/LUA/ff-esp32-openmppt/test/README.rst
@@ -0,0 +1,14 @@
+####################
+ISEMS-ESP32 dry-dock
+####################
+
+About
+=====
+This environment makes it possible to run parts of the
+ISEMS-ESP32 Node MCU Lua code on a vanilla PC.
+
+Synopsis
+========
+::
+
+ lua test/run_basic.lua
diff --git a/LUA/ff-esp32-openmppt/test/nodemcu_mock.lua b/LUA/ff-esp32-openmppt/test/nodemcu_mock.lua
new file mode 100644
index 0000000..4faaf6b
--- /dev/null
+++ b/LUA/ff-esp32-openmppt/test/nodemcu_mock.lua
@@ -0,0 +1,64 @@
+--[[
+Mock the API of a NodeMCU device.
+]]
+
+
+--[[
+Mocks for NodeMCU core modules.
+]]
+time = {
+ get = function()
+ return os.time()
+ end,
+ getlocal = function()
+ date = os.date("*t")
+ date["mon"] = date["month"]
+ date["dst"] = "1"
+ return date
+ end,
+}
+
+node = {
+ dsleep = function() end,
+}
+
+file = {
+ list = function() end,
+ exists = function() end,
+}
+
+gpio = {
+ config = function() end,
+ wakeup = function() end,
+ write = function() end,
+}
+
+dac = {
+ enable = function() end,
+}
+
+adc = {
+ setup = function() end,
+ setwidth = function() end,
+ read = function() return 42.42 end,
+}
+
+
+--[[
+Requires nodemcu-lua-mocks to be installed for JSON support.
+https://github.com/fikin/nodemcu-lua-mocks
+]]
+
+local sjson = require("sjson")
+sjson.encode = function(data)
+ encoder = sjson.encoder(data)
+ return encoder:read(8192)
+end
+
+
+--[[
+TODO: Add mocks for mqtt and http modules.
+Currently, "test/run_basic" will croak with::
+
+ lua: telemetry.lua:59: attempt to index a nil value (global 'mqtt')
+]]
diff --git a/LUA/ff-esp32-openmppt/test/run_basic.lua b/LUA/ff-esp32-openmppt/test/run_basic.lua
new file mode 100644
index 0000000..260c320
--- /dev/null
+++ b/LUA/ff-esp32-openmppt/test/run_basic.lua
@@ -0,0 +1,14 @@
+--[[
+Run a single duty cycle to completion.
+]]
+
+-- Bootstrap
+dofile "test/nodemcu_mock.lua"
+dofile "config.lua"
+
+-- No periodic execution.
+-- dofile "is.lua"
+
+-- Run cycle.
+Vref = 1100 --mV
+dofile "mp2.lua"