Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions drivers/leviton/acquisuite.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require "http"
require "placeos-driver"
require "csv"
require "action-controller/body_parser"
require "compress/gzip"

class Leviton::Acquisuite < PlaceOS::Driver
descriptive_name "Leviton Acquisuite Webhook"
Expand Down Expand Up @@ -31,6 +32,7 @@ class Leviton::Acquisuite < PlaceOS::Driver
end

def receive_webhook(method : String, headers : Hash(String, Array(String)), body : String)
body = Base64.decode_string(body)
logger.info do
"Received Webhook\n" +
"Method: #{method.inspect}\n" +
Expand All @@ -45,7 +47,10 @@ class Leviton::Acquisuite < PlaceOS::Driver
files, form_data = ActionController::BodyParser.extract_form_data(request, "multipart/form-data", request.query_params)
form_data = form_data.not_nil!
case form_data["MODE"]
# This is the server asking for a list of devices which we need the config files
# This is the server checking the status of our webhook so just 200 back
when "STATUS"
return {HTTP::Status::OK.to_i, {} of String => String, ""}
# This is the server asking for a list of devices which we need the config files
when "CONFIGFILEMANIFEST"
return {HTTP::Status::OK.to_i, {} of String => String, device_to_manifest.join("\n")}
# This is the server sending us an actual config file from the previously provided list
Expand All @@ -57,7 +62,7 @@ class Leviton::Acquisuite < PlaceOS::Driver
files = files.not_nil!
return log_file_upload(files, form_data)
else
{HTTP::Status::INTERNAL_SERVER_ERROR.to_i, {"Content-Type" => "application/json"}, "Invalid mode passed. Either CONFIGFILEMANIFEST, CONFIGFILEUPLOAD or LOGFILEUPLOAD required. Got #{form_data["MODE"]}"}
{HTTP::Status::INTERNAL_SERVER_ERROR.to_i, {"Content-Type" => "application/json"}, "Invalid mode passed. Either STATUS, CONFIGFILEMANIFEST, CONFIGFILEUPLOAD or LOGFILEUPLOAD required. Got #{form_data["MODE"]}"}
end
end
rescue error
Expand All @@ -67,7 +72,6 @@ class Leviton::Acquisuite < PlaceOS::Driver

protected def log_file_upload(files : Hash(String, Array(ActionController::BodyParser::FileUpload)), form_data : URI::Params)
log_file, log_contents = get_file(files, "LOGFILE")

# Check whether we have the config for this log file device type
modbus_index = form_data["MODBUSDEVICE"].to_i
if !@device_list.any? { |device, config| device.includes?("mb-%03d" % modbus_index) && config[0] != "X" }
Expand All @@ -76,7 +80,6 @@ class Leviton::Acquisuite < PlaceOS::Driver
define_setting(:device_list, @device_list)
return {HTTP::Status::NOT_ACCEPTABLE.to_i, {} of String => String, ""}
end

csv = CSV.new(log_file, headers: true)
# NOTE: This csv.next structure assumes that there will be a header row we don't need
# if this is not the case we should add logic to check for a header
Expand Down Expand Up @@ -118,8 +121,17 @@ class Leviton::Acquisuite < PlaceOS::Driver

protected def get_file(files : Hash(String, Array(ActionController::BodyParser::FileUpload)), name : String)
file = files.not_nil!
file_contents = file[name][0]
{file_contents.body.gets_to_end, file_contents}
file_object = file[name][0]
file_contents = file_object.body.gets_to_end
# If the file is gzipped then unzip it
file_name = file_object.filename
if file_name && file_name[-3..-1] == ".gz"
Compress::Gzip::Reader.open(IO::Memory.new(file_contents)) do |gzip|
gzip.gets_to_end
end
end

{file_contents, file_contents}
end

def device_list
Expand All @@ -128,14 +140,14 @@ class Leviton::Acquisuite < PlaceOS::Driver

protected def store_config(modbusid : String, config : String)
index_max = config.split("\n").map { |line|
reg = /POINT(?<index>\d*)(?<name>.*)=(?<value>.*)/.match(line)
reg = /POINT(?<index>\d+)(?<name>.*)=(?<value>.*)/.match(line)
reg[1].to_i if reg
}.compact.sort.pop

configs = Array.new(index_max + 1, {} of String => (Float64 | String))

config.split("\n").each do |line|
reg = /POINT(?<index>\d*)(?<name>.*)=(?<value>.*)/.match(line)
reg = /POINT(?<index>\d+)(?<name>.*)=(?<value>.*)/.match(line)
if reg
config_index = reg[1].to_i
column_header = reg[2]
Expand Down
36 changes: 18 additions & 18 deletions drivers/leviton/acquisuite_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ require "placeos-driver/spec"

DriverSpecs.mock_driver "Leviton::Acquisuite" do
headers = {"Content-Type" => ["multipart/form-data; boundary=MIME_BOUNDRY_MIME_BOUNDRY_MIME_BOUNDRY"]}
test_devices = ["mb-001", "mb-002"]
test_devices = ["mb-001"]

# First, we need to receive some failed LOGFILEUPLOAD requests to work out our list of devices
test_devices.each do |device_name|
dev_log = File.read("/app/repositories/local/drivers/leviton/#{device_name}.log")
dev_log = File.read("/app/repositories/local/drivers/leviton/#{device_name}.63BD5AFD_2.log.gz")
Comment thread
camreeves marked this conversation as resolved.
body = create_request(
"LOGFILEUPLOAD",
"Temp Inputs / Branch Circuits",
device_name[-1].to_s,
"9a6d278642b64db73c754271de733758",
"2022-09-12 21:25:55",
"LOGFILE",
"modbus/#{device_name}.log",
dev_log
"modbus/#{device_name}.63BD5AFD_2.log.gz",
Comment thread
camreeves marked this conversation as resolved.
nil
)
body = body.gsub("\n", "\r\n")
resp = exec(:receive_webhook, "POST", headers, body).get
body = body.gsub("fileplaceholder", dev_log)
resp = exec(:receive_webhook, "POST", headers, Base64.encode(body)).get

res = exec(:device_list).get
res = res.not_nil!
Expand All @@ -37,13 +38,10 @@ DriverSpecs.mock_driver "Leviton::Acquisuite" do
BODY

body = body.gsub("\n", "\r\n")
resp = exec(:receive_webhook, "POST", headers, body).get
resp = exec(:receive_webhook, "POST", headers, Base64.encode(body)).get

# We should expect the driver to respond with a manifest containing the list of devices
resp = resp.not_nil!
puts "MANIFEST RESPONSE:"
puts resp.inspect
puts resp[2].class
# resp[2].to_s.split("\n").size.should eq device_list.size if !resp.nil?

dev_config = File.read("/app/repositories/local/drivers/leviton/mb-001.ini")
Expand All @@ -52,7 +50,7 @@ DriverSpecs.mock_driver "Leviton::Acquisuite" do
body = create_request(
"CONFIGFILEUPLOAD",
"Temp Inputs / Branch Circuits",
"2",
"1",
"9a6d278642b64db73c754271de733758",
"2022-09-12 21:25:55",
"CONFIGFILE",
Expand All @@ -61,28 +59,30 @@ DriverSpecs.mock_driver "Leviton::Acquisuite" do
)

body = body.gsub("\n", "\r\n")
resp = exec(:receive_webhook, "POST", headers, body).get
resp = exec(:receive_webhook, "POST", headers, Base64.encode(body)).get

dev_log = File.read("/app/repositories/local/drivers/leviton/mb-001.log")
dev_log = File.read("/app/repositories/local/drivers/leviton/mb-001.63BD5AFD_2.log.gz")

# Now, finally, send an actual log file
body = create_request(
"LOGFILEUPLOAD",
"Temp Inputs / Branch Circuits",
"2",
"1",
"9a6d278642b64db73c754271de733758",
"2022-09-12 21:25:55",
"LOGFILE",
"tmp_name",
dev_log
"mb-001.63BD5AFD_2.log.gz",
nil
)
body = body.gsub("\n", "\r\n")
resp = exec(:receive_webhook, "POST", headers, body).get
body = body.gsub("fileplaceholder", dev_log)
resp = exec(:receive_webhook, "POST", headers, Base64.encode(body)).get
end

# Some of these fields may not be present in every request but
# having them there doesn't hurt anything so why bother removing them
def create_request(mode : String, device_name : String, modbus_device : String, md5 : String, file_time : String, file_descriptor : String, file_name : String, file : String)
def create_request(mode : String, device_name : String, modbus_device : String, md5 : String, file_time : String, file_descriptor : String, file_name : String, file : String?)
file = "fileplaceholder" if file.nil?
<<-BODY
--MIME_BOUNDRY_MIME_BOUNDRY_MIME_BOUNDRY
Content-Disposition: form-data; name="MODE"
Expand All @@ -109,7 +109,7 @@ def create_request(mode : String, device_name : String, modbus_device : String,
Content-Type: application/octet-stream;

#{file}

--MIME_BOUNDRY_MIME_BOUNDRY_MIME_BOUNDRY--

BODY
end
Binary file added drivers/leviton/mb-001.63BD5AFD_2.log.gz
Binary file not shown.
Loading