Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MQTT- and HTTP-based telemetry with JSON encoding #2

Merged
merged 3 commits into from Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions 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.
12 changes: 12 additions & 0 deletions LUA/ff-esp32-openmppt/config.lua
Expand Up @@ -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
Expand Down Expand Up @@ -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"
12 changes: 8 additions & 4 deletions LUA/ff-esp32-openmppt/mp2.lua
Expand Up @@ -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()

Expand All @@ -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
Expand All @@ -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<h1>Independent Solar Energy Mesh</h1><br><h2>Status of " .. nodeid
pagestring = pagestring .. " (local node)</h2><br><br>Summary: " .. charge_status .. ". " .. system_status
pagestring = pagestring .. "<br>Charge state: "
Expand Down
114 changes: 114 additions & 0 deletions 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
14 changes: 14 additions & 0 deletions 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
64 changes: 64 additions & 0 deletions 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')
]]
14 changes: 14 additions & 0 deletions 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"