OVMS3/OVMS.V3/components/max7317/src/max7317.cpp

567 lines
15 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2011 Michael Stegen / Stegen Electronics
; (C) 2011-2017 Mark Webb-Johnson
; (C) 2011 Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
static const char *TAG = "max7317";
#include <string.h>
#include "max7317.h"
#include "ovms_command.h"
#include "ovms_peripherals.h"
#include "ovms_config.h"
#include "ovms_events.h"
#include "metrics_standard.h"
max7317::max7317(const char* name, spi* spibus, spi_host_device_t host, int clockspeed, int cspin )
: pcp(name)
{
m_spibus = spibus;
m_clockspeed = clockspeed;
memset(&m_devcfg, 0, sizeof(spi_device_interface_config_t));
m_devcfg.clock_speed_hz=m_clockspeed; // Clock speed (in hz)
m_devcfg.mode=0; // SPI mode 0
m_devcfg.command_bits=0;
m_devcfg.address_bits=0;
m_devcfg.dummy_bits=0;
m_devcfg.spics_io_num=cspin;
m_devcfg.queue_size=7; // We want to be able to queue 7 transactions at a time
// Use the SPI object to determine if the SPI bus has been initialized
if (m_spibus->m_initialized == false)
{
esp_err_t ret = spi_bus_initialize(host, &m_spibus->m_buscfg, 0);
assert(ret==ESP_OK);
m_spibus->m_host = host;
m_spibus->m_initialized = true;
}
esp_err_t ret = spi_bus_add_device(host, &m_devcfg, &m_spi);
assert(ret==ESP_OK);
m_monitor_task = NULL;
m_monitor_timer = NULL;
m_inputstate = ~0;
m_outputstate = ~0; // power up: all ports in input mode (high)
m_monitor_ports = 0;
*StdMetrics.ms_m_egpio_input = m_inputstate;
*StdMetrics.ms_m_egpio_output = m_outputstate;
*StdMetrics.ms_m_egpio_monitor = m_monitor_ports;
}
max7317::~max7317()
{
if (m_monitor_timer)
delete m_monitor_timer;
if (m_monitor_task)
vTaskDelete(m_monitor_task);
}
std::bitset<10> max7317::GetConfigMonitorPorts()
{
std::bitset<10> ports;
// Parse configuration: list of port numbers separated by spaces
std::string value = MyConfig.GetParamValue("egpio", "monitor.ports");
std::istringstream vs(value);
std::string token;
int port;
while (std::getline(vs, token, ' '))
{
std::istringstream ts(token);
ts >> port;
if (port >= 0 && port <= 9)
ports[port] = 1;
}
return ports;
}
void max7317::AutoInit()
{
if (MyConfig.GetParamValueBool("auto", "egpio", false))
{
m_monitor_ports = GetConfigMonitorPorts();
if (!CheckMonitor())
ESP_LOGE(TAG, "AutoInit: monitoring could not be started");
}
}
/**
* Output: set specific port output state
*/
void max7317::Output(uint8_t port, uint8_t level)
{
uint8_t buf[4];
// Set port:
m_spibus->spi_cmd(m_spi, buf, 0, 2, port, level);
// Update state & framework:
if (!m_outputstate[port] && level)
{
m_outputstate[port] = true;
*StdMetrics.ms_m_egpio_output = m_outputstate;
std::string event = "egpio.output.";
event.append(1, '0'+port);
event.append(".high");
ESP_LOGD(TAG, "%s", event.c_str());
MyEvents.SignalEvent(event, NULL);
}
else if (m_outputstate[port] && !level)
{
m_outputstate[port] = false;
*StdMetrics.ms_m_egpio_output = m_outputstate;
std::string event = "egpio.output.";
event.append(1, '0'+port);
event.append(".low");
ESP_LOGD(TAG, "%s", event.c_str());
MyEvents.SignalEvent(event, NULL);
}
}
std::bitset<10> max7317::OutputState()
{
return m_outputstate;
}
/**
* Input: read specific port input state
*/
uint8_t max7317::Input(uint8_t port)
{
uint8_t buf[2];
uint8_t level;
OvmsMutexLock lock(&m_monitor_mutex);
// Read port state:
// Note: MAX7317 SPI read needs a deselect between tx and rx (see specs pg. 8).
// SPI always does tx & rx in parallel, so we must send a NOP (0x20,0) on the "rx"
// command, as the MAX7317 would interpret (0,0) as a write of level 0 to port 0.
if (port < 8)
{
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x8E, 0);
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x20, 0);
level = (buf[1] & (1 << port)) ? 1 : 0;
}
else
{
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x8F, 0);
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x20, 0);
level = (buf[1] & (1 << (port-8))) ? 1 : 0;
}
// Update state & framework:
if (m_inputstate[port] != level)
{
std::bitset<10> pstate = m_inputstate;
pstate[port] = level;
*StdMetrics.ms_m_egpio_input = pstate;
if (!m_inputstate[port] && level)
{
std::string event = "egpio.input.";
event.append(1, '0'+port);
event.append(".high");
ESP_LOGD(TAG, "%s", event.c_str());
MyEvents.SignalEvent(event, NULL);
}
else if (m_inputstate[port] && !level)
{
std::string event = "egpio.input.";
event.append(1, '0'+port);
event.append(".low");
ESP_LOGD(TAG, "%s", event.c_str());
MyEvents.SignalEvent(event, NULL);
}
m_inputstate[port] = level;
}
return level;
}
/**
* Inputs: read input states for a set of ports
*/
std::bitset<10> max7317::Inputs(std::bitset<10> ports)
{
uint8_t buf[2];
std::bitset<10> pstate;
if (ports.none())
return pstate;
OvmsMutexLock lock(&m_monitor_mutex);
// Read port state: see notes above
if ((ports & std::bitset<10>(0b0011111111)).any())
{
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x8E, 0);
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x20, 0);
pstate = buf[1];
}
if ((ports & std::bitset<10>(0b1100000000)).any())
{
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x8F, 0);
m_spibus->spi_cmd(m_spi, buf, 0, 2, 0x20, 0);
pstate |= ((uint16_t)buf[1] << 8);
}
// Update state & framework:
pstate &= ports;
pstate |= (m_inputstate & ~ports);
*StdMetrics.ms_m_egpio_input = pstate;
if ((m_inputstate ^ pstate).any())
{
std::string event;
for (int i=0; i<10; i++)
{
if (!ports[i])
continue;
else if (!m_inputstate[i] && pstate[i])
{
event = "egpio.input.";
event.append(1, '0'+i);
event.append(".high");
ESP_LOGD(TAG, "%s", event.c_str());
MyEvents.SignalEvent(event, NULL);
}
else if (m_inputstate[i] && !pstate[i])
{
event = "egpio.input.";
event.append(1, '0'+i);
event.append(".low");
ESP_LOGD(TAG, "%s", event.c_str());
MyEvents.SignalEvent(event, NULL);
}
}
}
m_inputstate = pstate;
return pstate;
}
std::bitset<10> max7317::InputState()
{
return m_inputstate;
}
/**
* MonitorTask: poll input state
*/
static void MonitorTaskEntry(void* me)
{
((max7317*)me)->MonitorTask();
}
void max7317::MonitorTask()
{
while (true)
{
if (m_monitor_semaphore.Take())
Inputs(m_monitor_ports);
}
}
/**
* CheckMonitor: activate/deactivate port input monitoring as necessary
*/
bool max7317::CheckMonitor()
{
*StdMetrics.ms_m_egpio_monitor = m_monitor_ports;
if (m_monitor_ports.any())
{
// activate monitoring:
if (!m_monitor_task)
{
xTaskCreatePinnedToCore(MonitorTaskEntry, "OVMS EGPIOmon",
4*512, (void*)this, 15, &m_monitor_task, CORE(1));
if (!m_monitor_task)
{
ESP_LOGE(TAG, "Monitor: unable to create task");
return false;
}
}
if (!m_monitor_timer)
{
m_monitor_timer = new OvmsInterval("EGPIOmon");
if (!m_monitor_timer)
{
ESP_LOGE(TAG, "Monitor: unable to create timer");
return false;
}
}
if (!m_monitor_timer->IsActive())
{
int interval = MyConfig.GetParamValueInt("egpio", "monitor.interval",
CONFIG_OVMS_COMP_MAX7317_MONITOR_INTERVAL);
if (interval < 10)
interval = 10;
if (!m_monitor_timer->Start(interval, [&]{ m_monitor_semaphore.Give(); }))
{
ESP_LOGE(TAG, "Monitor: unable to start timer");
return false;
}
}
}
else
{
// deactivate monitoring:
if (m_monitor_timer && !m_monitor_timer->Stop())
{
ESP_LOGW(TAG, "Monitor: unable to stop timer");
return false;
}
}
return true;
}
/**
* Monitor: enable/disable input monitoring for specific ports
*/
bool max7317::Monitor(std::bitset<10> ports, bool enable)
{
OvmsMutexLock lock(&m_monitor_mutex);
if (enable)
m_monitor_ports |= ports;
else
m_monitor_ports &= ~ports;
return CheckMonitor();
}
bool max7317::Monitor(uint8_t port, bool enable)
{
OvmsMutexLock lock(&m_monitor_mutex);
m_monitor_ports[port] = enable;
return CheckMonitor();
}
std::bitset<10> max7317::MonitorState()
{
OvmsMutexLock lock(&m_monitor_mutex);
return m_monitor_ports;
}
void max7317_output(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
for (int i = 0; i < argc-1; i += 2)
{
int port = atoi(argv[i]);
if ((port <0)||(port>9))
{
writer->puts("Error: Port should be in range 0..9");
return;
}
int level = atoi(argv[i+1]);
if ((level<0)||(level>255))
{
writer->puts("Error: Level should be in range 0..255");
return;
}
MyPeripherals->m_max7317->Output((uint8_t)port,(uint8_t)level);
writer->printf("EGPIO port %d set to output level %d\n", port, level);
}
}
void max7317_pulse(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int port = atoi(argv[0]);
int level = atoi(argv[1]);
int durationms = atoi(argv[2]);
int baselevel = 1-level;
writer->printf("EGPIO port %d set to base level %d\n", port, baselevel);
MyPeripherals->m_max7317->Output((uint8_t)port,(uint8_t)baselevel);
writer->printf("EGPIO port %d setting to level %d for %dms\n", port, level, durationms);
MyPeripherals->m_max7317->Output((uint8_t)port,(uint8_t)level);
vTaskDelay(pdMS_TO_TICKS(durationms));
writer->printf("EGPIO port %d set back to base level %d\n", port, baselevel);
MyPeripherals->m_max7317->Output((uint8_t)port,(uint8_t)baselevel);
}
void max7317_input(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
for (int i = 0; i < argc; i += 1)
{
int port = atoi(argv[i]);
if ((port <0)||(port>9))
{
writer->puts("Error: Port should be in range 0..9");
return;
}
MyPeripherals->m_max7317->Output((uint8_t)port,(uint8_t)1); // set port to input
int level = MyPeripherals->m_max7317->Input((uint8_t)port);
writer->printf("EGPIO port %d has input level %d\n", port, level);
}
}
void max7317_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd,
int argc, const char* const* argv)
{
max7317* me = MyPeripherals->m_max7317;
auto printports = [writer](const char* intro, std::bitset<10> ports)
{
writer->printf("%s", intro);
for (int i=0; i<10; i++)
writer->printf(" %d:%d", i, ports.test(i));
writer->puts("");
};
writer->puts("EGPIO port status:");
printports("Output :", me->OutputState());
printports("Input :", me->InputState());
printports("Monitor:", me->MonitorState());
}
void max7317_monitor(int verbosity, OvmsWriter* writer, OvmsCommand* cmd,
int argc, const char* const* argv)
{
max7317* me = MyPeripherals->m_max7317;
std::bitset<10> ports;
// Enable/disable port monitoring?
if (strcmp(cmd->GetName(), "status") != 0)
{
bool enable = (strcmp(cmd->GetName(), "on") == 0);
for (int i=0; i<argc; i++)
{
int port = atoi(argv[i]);
if (port < 0 || port > 9)
{
writer->puts("Error: ports must be in range 0..9");
}
else
{
ports[port] = 1;
}
}
// no ports given on command line?
if (ports.none())
{
if (enable)
{
// enable configured ports:
ports = me->GetConfigMonitorPorts();
if (ports.none())
writer->puts("ERROR: no ports configured");
}
else
{
// disable all enabled ports:
ports = me->MonitorState();
}
}
// change monitoring:
if (ports.any())
{
if (enable)
{
// set ports to input mode:
for (int i=0; i<10; i++)
if (ports[i]) me->Output(i, 1);
}
if (me->Monitor(ports, enable))
{
writer->printf("OK: input monitoring %s for %d port%s.\n",
enable ? "enabled" : "disabled",
ports.count(),
ports.count() == 1 ? "" : "s");
}
else
{
writer->printf("ERROR: %s input monitoring failed (see logs for details)\n",
enable ? "enabling" : "disabling");
}
}
}
// Show monitoring status:
ports = me->MonitorState();
if (ports.none())
{
writer->puts("Status: input monitoring disabled for all ports.");
}
else if (ports.all())
{
writer->puts("Status: input monitoring enabled for all ports.");
}
else
{
writer->printf("Status: input monitoring enabled for ports:");
for (int i=0; i<10; i++)
{
if (ports[i])
writer->printf(" %d", i);
}
writer->puts("");
}
}
class Max7317Init
{
public: Max7317Init();
} MyMax7317Init __attribute__ ((init_priority (4200)));
Max7317Init::Max7317Init()
{
ESP_LOGI(TAG, "Initialising MAX7317 EGPIO (4200)");
MyConfig.RegisterParam("egpio", "EGPIO configuration", true, true);
OvmsCommand* cmd_egpio = MyCommandApp.RegisterCommand(
"egpio", "EGPIO framework", max7317_status, "", 0, 0, false);
cmd_egpio->RegisterCommand(
"output", "Set EGPIO output level(s)", max7317_output,
"<port> <level> [<port> <level> ...]", 2, 20);
cmd_egpio->RegisterCommand(
"pulse", "Pulse EGPIO output level(s)", max7317_pulse,
"<port> <level> <durationms>", 3, 3);
cmd_egpio->RegisterCommand(
"input", "Get EGPIO input level(s)", max7317_input,
"<port> [<port> ...]", 1, 10);
cmd_egpio->RegisterCommand(
"status", "Show EGPIO status", max7317_status);
OvmsCommand* cmd_mon = cmd_egpio->RegisterCommand(
"monitor", "EGPIO input monitoring");
cmd_mon->RegisterCommand(
"status", "Show input monitoring status", max7317_monitor);
cmd_mon->RegisterCommand(
"on", "Enable input monitoring", max7317_monitor,
"[<port> ...]\n"
"No ports = enable as configured in egpio/monitor.ports\n"
"Note: ports specified will be switched to input mode", 0, 10);
cmd_mon->RegisterCommand(
"off", "Disable input monitoring", max7317_monitor,
"[<port> ...]\n"
"No ports = disable all", 0, 10);
}