Skip to content

Commit a43d5f9

Browse files
committed
Add Output: GP8403 2-Channel DAC (0-10 VDC) (#1354)
1 parent df64ee7 commit a43d5f9

File tree

4 files changed

+263
-1
lines changed

4 files changed

+263
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This release changes the install directory from ~/Mycodo to /opt/Mycodo. This ne
1515

1616
- Add Output: GPIO On/Off using pinctrl (First Pi 5-compatible Output)
1717
- Add Output: PWM MQTT Publish
18+
- Add Output: GP8403 2-Channel DAC (0-10 VDC) ([#1354](https://github.com/kizniche/Mycodo/issues/1354))
1819
- Add API Endpoint: /notes/create to create a Note ([#1357](https://github.com/kizniche/Mycodo/issues/1357))
1920
- Add ability to switch displaying hostname with custom text
2021
- Add Step Line Series Type to Graph (Synchronous) Widget

mycodo/mycodo_flask/routes_static.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def inject_variables():
7171
languages_sorted = sorted(LANGUAGES.items(), key=operator.itemgetter(1))
7272

7373
return dict(current_user=flask_login.current_user,
74+
custom_css=misc.custom_css,
7475
dark_themes=THEMES_DARK,
7576
daemon_status=daemon_status,
7677
dashboards=dashboards,

mycodo/mycodo_flask/templates/layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169

170170
</script>
171171

172-
{% if misc.custom_css %}
172+
{% if custom_css %}
173173
<link href="/custom.css" rel="stylesheet">
174174
{% endif %}
175175

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# coding=utf-8
2+
#
3+
# value_gp8403_dac_0_10_vdc.py - Output for controlling the GP8403 2-channel DAC (0-10 VDC)
4+
#
5+
import copy
6+
import time
7+
8+
from flask_babel import lazy_gettext
9+
10+
from mycodo.databases.models import OutputChannel
11+
from mycodo.outputs.base_output import AbstractOutput
12+
from mycodo.utils.constraints_pass import constraints_pass_positive_or_zero_value
13+
from mycodo.utils.database import db_retrieve_table_daemon
14+
from mycodo.utils.influx import add_measurements_influxdb
15+
16+
measurements_dict = {
17+
0: {
18+
'measurement': 'electrical_potential',
19+
'unit': 'V'
20+
},
21+
1: {
22+
'measurement': 'electrical_potential',
23+
'unit': 'V'
24+
}
25+
}
26+
27+
channels_dict = {
28+
0: {
29+
'types': ['value'],
30+
'measurements': [0]
31+
},
32+
1: {
33+
'types': ['value'],
34+
'measurements': [1]
35+
}
36+
}
37+
38+
OUTPUT_INFORMATION = {
39+
'output_name_unique': 'OUTPUT_GP8403_DAC_0_10_VDC',
40+
'output_name': "{}: GP8403 2-Channel DAC: 0-10 VDC".format(lazy_gettext('Value')),
41+
'output_library': 'DFRobot-GP8403',
42+
'output_manufacturer': 'Mycodo',
43+
'measurements_dict': measurements_dict,
44+
'channels_dict': channels_dict,
45+
'output_types': ['value'],
46+
47+
'url_additional': 'https://www.dfrobot.com/product-2613.html',
48+
49+
'message': 'Output a 0 to 10 VDC signal.',
50+
51+
'dependencies_module': [
52+
('pip-pypi', 'DFRobot.DAC', 'DFRobot-GP8403==0.1.1')
53+
],
54+
55+
'options_enabled': [
56+
'i2c_location',
57+
'button_send_value'
58+
],
59+
'options_disabled': ['interface'],
60+
61+
'interfaces': ['I2C'],
62+
'i2c_location': ['0x58'],
63+
'i2c_address_editable': True,
64+
65+
'custom_channel_options': [
66+
{
67+
'id': 'state_start',
68+
'type': 'select',
69+
'default_value': 'value',
70+
'options_select': [
71+
('saved', 'Previously-Saved State'),
72+
('value', 'Specified Value')
73+
],
74+
'name': 'Start State',
75+
'phrase': 'Select the channel start state'
76+
},
77+
{
78+
'id': 'state_start_value',
79+
'type': 'float',
80+
'default_value': 0,
81+
'constraints_pass': constraints_pass_positive_or_zero_value,
82+
'name': 'Start Value (volts)',
83+
'phrase': 'If Specified Value is selected, set the start state value'
84+
},
85+
{
86+
'id': 'state_shutdown',
87+
'type': 'select',
88+
'default_value': 'value',
89+
'options_select': [
90+
('saved', 'Previously-Saved Value'),
91+
('value', 'Specified Value')
92+
],
93+
'name': 'Shutdown State',
94+
'phrase': 'Select the channel shutdown state'
95+
},
96+
{
97+
'id': 'state_shutdown_value',
98+
'type': 'float',
99+
'default_value': 0,
100+
'constraints_pass': constraints_pass_positive_or_zero_value,
101+
'name': 'Shutdown Value (volts)',
102+
'phrase': 'If Specified Value is selected, set the shutdown state value'
103+
},
104+
{
105+
'id': 'off_value',
106+
'type': 'float',
107+
'default_value': 0,
108+
'constraints_pass': constraints_pass_positive_or_zero_value,
109+
'name': 'Off Value (volts)',
110+
'phrase': 'If Specified Value to apply when turned off'
111+
}
112+
]
113+
}
114+
115+
116+
class OutputModule(AbstractOutput):
117+
"""An output support class that operates an output."""
118+
def __init__(self, output, testing=False):
119+
super().__init__(output, testing=testing, name=__name__)
120+
121+
self.GP8403 = None
122+
123+
output_channels = db_retrieve_table_daemon(
124+
OutputChannel).filter(OutputChannel.output_id == self.output.unique_id).all()
125+
self.options_channels = self.setup_custom_channel_options_json(
126+
OUTPUT_INFORMATION['custom_channel_options'], output_channels)
127+
128+
def initialize(self):
129+
from DFRobot.DAC import GP8403
130+
131+
self.GP8403 = GP8403(bus=self.output.i2c_bus, addr=int(str(self.output.i2c_location), 16))
132+
133+
self.setup_output_variables(OUTPUT_INFORMATION)
134+
135+
timer = time.time()
136+
while self.GP8403.begin() != 0:
137+
self.logger.error("init error")
138+
if time.time() - timer > 5: # Max 5 second wait
139+
self.logger.error("Could not initialize after 5 seconds")
140+
return
141+
time.sleep(1)
142+
143+
self.GP8403.set_DAC_outrange(self.GP8403.OUTPUT_RANGE_10V)
144+
145+
# Set start channel values
146+
for channel in channels_dict:
147+
value_volts = None
148+
149+
if channel == 0:
150+
chan = self.GP8403.CHANNEL0
151+
elif channel == 1:
152+
chan = self.GP8403.CHANNEL1
153+
else:
154+
return
155+
156+
if (self.options_channels['state_start'][channel] == "value" and
157+
self.options_channels['state_start_value'][channel]):
158+
value_volts = self.options_channels['state_start_value'][channel]
159+
elif (self.options_channels['state_start'][channel] == "saved" and
160+
self.get_custom_option(f"saved_channel_{channel}_value") is not None):
161+
value_volts = self.get_custom_option(f"saved_channel_{channel}_value")
162+
163+
if value_volts is not None:
164+
if self.options_channels['state_start_value'][channel] > 10:
165+
self.logger.error(f"Startup value cannot be greater than 10 VDC. Value provided: {value_volts}")
166+
value = 4096
167+
elif self.options_channels['state_start_value'][channel] < 0:
168+
self.logger.error(f"Startup value cannot be less than 0 VDC. Value provided: {value_volts}")
169+
value = 0
170+
else:
171+
value = value_volts / 10 * 4096
172+
self.GP8403.set_DAC_out_voltage(value, chan)
173+
174+
self.output_setup = True
175+
176+
def output_switch(self, state, output_type=None, amount=None, output_channel=0):
177+
measure_dict = copy.deepcopy(measurements_dict)
178+
179+
try:
180+
if output_channel == 0:
181+
chan = self.GP8403.CHANNEL0
182+
elif output_channel == 1:
183+
chan = self.GP8403.CHANNEL1
184+
else:
185+
self.logger.error(f"Invalid channel: {output_channel}")
186+
return
187+
188+
if state == 'on' and amount is not None:
189+
if amount > 10:
190+
self.logger.error(f"Startup value cannot be greater than 10 VDC. Value provided: {amount}")
191+
value = 4096
192+
elif amount < 0:
193+
self.logger.error(f"Startup value cannot be less than 0 VDC. Value provided: {amount}")
194+
value = 0
195+
else:
196+
value = amount / 10 * 4096
197+
198+
self.GP8403.set_DAC_out_voltage(value, chan)
199+
self.output_states[output_channel] = amount
200+
measure_dict[output_channel]['value'] = amount
201+
202+
self.set_custom_option(f"saved_channel_{output_channel}_value", amount)
203+
elif state == 'off':
204+
self.GP8403.set_DAC_out_voltage(self.options_channels['off_value'][output_channel], chan)
205+
if self.options_channels['off_value'][output_channel]:
206+
self.output_states[output_channel] = self.options_channels['off_value'][output_channel]
207+
else:
208+
self.output_states[output_channel] = False
209+
measure_dict[output_channel]['value'] = self.options_channels['off_value'][output_channel]
210+
211+
self.set_custom_option(f"saved_channel_{output_channel}_value", amount)
212+
except Exception as e:
213+
self.logger.error("State change error: {}".format(e))
214+
return
215+
216+
add_measurements_influxdb(self.unique_id, measure_dict)
217+
218+
def is_on(self, output_channel=0):
219+
if self.is_setup():
220+
if output_channel is not None and output_channel in self.output_states:
221+
return self.output_states[output_channel]
222+
else:
223+
return self.output_states
224+
225+
def is_setup(self):
226+
return self.output_setup
227+
228+
def stop_output(self):
229+
"""Called when Output is stopped."""
230+
if self.is_setup():
231+
for channel in channels_dict:
232+
value_volts = None
233+
234+
if channel == 0:
235+
chan = self.GP8403.CHANNEL0
236+
elif channel == 1:
237+
chan = self.GP8403.CHANNEL1
238+
else:
239+
continue
240+
241+
if (self.options_channels['state_shutdown'][channel] == "value" and
242+
self.options_channels['state_shutdown_value'][channel]):
243+
value_volts = self.options_channels['state_shutdown_value'][channel]
244+
elif (self.options_channels['state_shutdown'][channel] == "saved" and
245+
self.get_custom_option(f"saved_channel_{channel}_value") is not None):
246+
value_volts = self.get_custom_option(f"saved_channel_{channel}_value")
247+
248+
if value_volts is not None:
249+
if value_volts > 10:
250+
self.logger.error(f"Startup value cannot be greater than 10 VDC. Value provided: {value_volts}")
251+
value = 4096
252+
elif value_volts < 0:
253+
self.logger.error(f"Startup value cannot be less than 0 VDC. Value provided: {value_volts}")
254+
value = 0
255+
else:
256+
value = value_volts / 10 * 4096
257+
258+
self.GP8403.set_DAC_out_voltage(value, chan)
259+
260+
self.running = False

0 commit comments

Comments
 (0)