Skip to content

Commit

Permalink
Add Output: GP8403 2-Channel DAC (0-10 VDC) (#1354)
Browse files Browse the repository at this point in the history
  • Loading branch information
kizniche committed Mar 6, 2024
1 parent df64ee7 commit a43d5f9
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This release changes the install directory from ~/Mycodo to /opt/Mycodo. This ne

- Add Output: GPIO On/Off using pinctrl (First Pi 5-compatible Output)
- Add Output: PWM MQTT Publish
- Add Output: GP8403 2-Channel DAC (0-10 VDC) ([#1354](https://github.com/kizniche/Mycodo/issues/1354))
- Add API Endpoint: /notes/create to create a Note ([#1357](https://github.com/kizniche/Mycodo/issues/1357))
- Add ability to switch displaying hostname with custom text
- Add Step Line Series Type to Graph (Synchronous) Widget
Expand Down
1 change: 1 addition & 0 deletions mycodo/mycodo_flask/routes_static.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def inject_variables():
languages_sorted = sorted(LANGUAGES.items(), key=operator.itemgetter(1))

return dict(current_user=flask_login.current_user,
custom_css=misc.custom_css,
dark_themes=THEMES_DARK,
daemon_status=daemon_status,
dashboards=dashboards,
Expand Down
2 changes: 1 addition & 1 deletion mycodo/mycodo_flask/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@

</script>

{% if misc.custom_css %}
{% if custom_css %}
<link href="/custom.css" rel="stylesheet">
{% endif %}

Expand Down
260 changes: 260 additions & 0 deletions mycodo/outputs/value_p8403_dac_0_10_vdc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# coding=utf-8
#
# value_gp8403_dac_0_10_vdc.py - Output for controlling the GP8403 2-channel DAC (0-10 VDC)
#
import copy
import time

from flask_babel import lazy_gettext

from mycodo.databases.models import OutputChannel
from mycodo.outputs.base_output import AbstractOutput
from mycodo.utils.constraints_pass import constraints_pass_positive_or_zero_value
from mycodo.utils.database import db_retrieve_table_daemon
from mycodo.utils.influx import add_measurements_influxdb

measurements_dict = {
0: {
'measurement': 'electrical_potential',
'unit': 'V'
},
1: {
'measurement': 'electrical_potential',
'unit': 'V'
}
}

channels_dict = {
0: {
'types': ['value'],
'measurements': [0]
},
1: {
'types': ['value'],
'measurements': [1]
}
}

OUTPUT_INFORMATION = {
'output_name_unique': 'OUTPUT_GP8403_DAC_0_10_VDC',
'output_name': "{}: GP8403 2-Channel DAC: 0-10 VDC".format(lazy_gettext('Value')),
'output_library': 'DFRobot-GP8403',
'output_manufacturer': 'Mycodo',
'measurements_dict': measurements_dict,
'channels_dict': channels_dict,
'output_types': ['value'],

'url_additional': 'https://www.dfrobot.com/product-2613.html',

'message': 'Output a 0 to 10 VDC signal.',

'dependencies_module': [
('pip-pypi', 'DFRobot.DAC', 'DFRobot-GP8403==0.1.1')
],

'options_enabled': [
'i2c_location',
'button_send_value'
],
'options_disabled': ['interface'],

'interfaces': ['I2C'],
'i2c_location': ['0x58'],
'i2c_address_editable': True,

'custom_channel_options': [
{
'id': 'state_start',
'type': 'select',
'default_value': 'value',
'options_select': [
('saved', 'Previously-Saved State'),
('value', 'Specified Value')
],
'name': 'Start State',
'phrase': 'Select the channel start state'
},
{
'id': 'state_start_value',
'type': 'float',
'default_value': 0,
'constraints_pass': constraints_pass_positive_or_zero_value,
'name': 'Start Value (volts)',
'phrase': 'If Specified Value is selected, set the start state value'
},
{
'id': 'state_shutdown',
'type': 'select',
'default_value': 'value',
'options_select': [
('saved', 'Previously-Saved Value'),
('value', 'Specified Value')
],
'name': 'Shutdown State',
'phrase': 'Select the channel shutdown state'
},
{
'id': 'state_shutdown_value',
'type': 'float',
'default_value': 0,
'constraints_pass': constraints_pass_positive_or_zero_value,
'name': 'Shutdown Value (volts)',
'phrase': 'If Specified Value is selected, set the shutdown state value'
},
{
'id': 'off_value',
'type': 'float',
'default_value': 0,
'constraints_pass': constraints_pass_positive_or_zero_value,
'name': 'Off Value (volts)',
'phrase': 'If Specified Value to apply when turned off'
}
]
}


class OutputModule(AbstractOutput):
"""An output support class that operates an output."""
def __init__(self, output, testing=False):
super().__init__(output, testing=testing, name=__name__)

self.GP8403 = None

output_channels = db_retrieve_table_daemon(
OutputChannel).filter(OutputChannel.output_id == self.output.unique_id).all()
self.options_channels = self.setup_custom_channel_options_json(
OUTPUT_INFORMATION['custom_channel_options'], output_channels)

def initialize(self):
from DFRobot.DAC import GP8403

self.GP8403 = GP8403(bus=self.output.i2c_bus, addr=int(str(self.output.i2c_location), 16))

self.setup_output_variables(OUTPUT_INFORMATION)

timer = time.time()
while self.GP8403.begin() != 0:
self.logger.error("init error")
if time.time() - timer > 5: # Max 5 second wait
self.logger.error("Could not initialize after 5 seconds")
return
time.sleep(1)

self.GP8403.set_DAC_outrange(self.GP8403.OUTPUT_RANGE_10V)

# Set start channel values
for channel in channels_dict:
value_volts = None

if channel == 0:
chan = self.GP8403.CHANNEL0
elif channel == 1:
chan = self.GP8403.CHANNEL1
else:
return

if (self.options_channels['state_start'][channel] == "value" and
self.options_channels['state_start_value'][channel]):
value_volts = self.options_channels['state_start_value'][channel]
elif (self.options_channels['state_start'][channel] == "saved" and
self.get_custom_option(f"saved_channel_{channel}_value") is not None):
value_volts = self.get_custom_option(f"saved_channel_{channel}_value")

if value_volts is not None:
if self.options_channels['state_start_value'][channel] > 10:
self.logger.error(f"Startup value cannot be greater than 10 VDC. Value provided: {value_volts}")
value = 4096
elif self.options_channels['state_start_value'][channel] < 0:
self.logger.error(f"Startup value cannot be less than 0 VDC. Value provided: {value_volts}")
value = 0
else:
value = value_volts / 10 * 4096
self.GP8403.set_DAC_out_voltage(value, chan)

self.output_setup = True

def output_switch(self, state, output_type=None, amount=None, output_channel=0):
measure_dict = copy.deepcopy(measurements_dict)

try:
if output_channel == 0:
chan = self.GP8403.CHANNEL0
elif output_channel == 1:
chan = self.GP8403.CHANNEL1
else:
self.logger.error(f"Invalid channel: {output_channel}")
return

if state == 'on' and amount is not None:
if amount > 10:
self.logger.error(f"Startup value cannot be greater than 10 VDC. Value provided: {amount}")
value = 4096
elif amount < 0:
self.logger.error(f"Startup value cannot be less than 0 VDC. Value provided: {amount}")
value = 0
else:
value = amount / 10 * 4096

self.GP8403.set_DAC_out_voltage(value, chan)
self.output_states[output_channel] = amount
measure_dict[output_channel]['value'] = amount

self.set_custom_option(f"saved_channel_{output_channel}_value", amount)
elif state == 'off':
self.GP8403.set_DAC_out_voltage(self.options_channels['off_value'][output_channel], chan)
if self.options_channels['off_value'][output_channel]:
self.output_states[output_channel] = self.options_channels['off_value'][output_channel]
else:
self.output_states[output_channel] = False
measure_dict[output_channel]['value'] = self.options_channels['off_value'][output_channel]

self.set_custom_option(f"saved_channel_{output_channel}_value", amount)
except Exception as e:
self.logger.error("State change error: {}".format(e))
return

add_measurements_influxdb(self.unique_id, measure_dict)

def is_on(self, output_channel=0):
if self.is_setup():
if output_channel is not None and output_channel in self.output_states:
return self.output_states[output_channel]
else:
return self.output_states

def is_setup(self):
return self.output_setup

def stop_output(self):
"""Called when Output is stopped."""
if self.is_setup():
for channel in channels_dict:
value_volts = None

if channel == 0:
chan = self.GP8403.CHANNEL0
elif channel == 1:
chan = self.GP8403.CHANNEL1
else:
continue

if (self.options_channels['state_shutdown'][channel] == "value" and
self.options_channels['state_shutdown_value'][channel]):
value_volts = self.options_channels['state_shutdown_value'][channel]
elif (self.options_channels['state_shutdown'][channel] == "saved" and
self.get_custom_option(f"saved_channel_{channel}_value") is not None):
value_volts = self.get_custom_option(f"saved_channel_{channel}_value")

if value_volts is not None:
if value_volts > 10:
self.logger.error(f"Startup value cannot be greater than 10 VDC. Value provided: {value_volts}")
value = 4096
elif value_volts < 0:
self.logger.error(f"Startup value cannot be less than 0 VDC. Value provided: {value_volts}")
value = 0
else:
value = value_volts / 10 * 4096

self.GP8403.set_DAC_out_voltage(value, chan)

self.running = False

0 comments on commit a43d5f9

Please sign in to comment.