From c7b7fdf8fab32d0bac19316cffa084b2131b7511 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 12 Aug 2021 00:15:55 +0200 Subject: [PATCH] New Plugin: MCP3008 SPI DAC --- debian/control | 10 ++ debian/nymea-plugin-mcp3008.install.in | 3 + i2cdevices/integrationplugini2cdevices.json | 2 +- mcp3008/README.md | 19 +++ mcp3008/integrationpluginmcp3008.json | 140 ++++++++++++++++++++ mcp3008/integrationpluginmcp3008.py | 116 ++++++++++++++++ mcp3008/mcp3008.pro | 6 + mcp3008/meta.json | 13 ++ mcp3008/requirements.txt | 3 + nymea-plugins.pro | 1 + 10 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 debian/nymea-plugin-mcp3008.install.in create mode 100644 mcp3008/README.md create mode 100644 mcp3008/integrationpluginmcp3008.json create mode 100644 mcp3008/integrationpluginmcp3008.py create mode 100644 mcp3008/mcp3008.pro create mode 100644 mcp3008/meta.json create mode 100644 mcp3008/requirements.txt diff --git a/debian/control b/debian/control index ccb78d60..bad99471 100644 --- a/debian/control +++ b/debian/control @@ -479,6 +479,15 @@ Description: nymea.io plugin for mailnotification This package will install the nymea.io plugin for mailnotification +Package: nymea-plugin-mcp3008 +Architecture: any +Depends: ${misc:Depends}, + nymea-plugins-translations, + python3-pip, +Description: MCP3008 DAC integration plugin for nymea + This package will install the nymea.io plugin for using the MCP3008 DAC. + + Package: nymea-plugin-mqttclient Architecture: any Depends: ${shlibs:Depends}, @@ -1279,6 +1288,7 @@ Depends: nymea-plugin-boblight, nymea-plugin-gpio, nymea-plugin-i2cdevices, nymea-plugin-mqttclient, + nymea-plugin-mcp3008, nymea-plugin-remotessh, nymea-plugin-serialportcommander, nymea-plugin-systemmonitor, diff --git a/debian/nymea-plugin-mcp3008.install.in b/debian/nymea-plugin-mcp3008.install.in new file mode 100644 index 00000000..813a722a --- /dev/null +++ b/debian/nymea-plugin-mcp3008.install.in @@ -0,0 +1,3 @@ +mcp3008/integrationpluginmcp3008.json usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/mcp3008/ +mcp3008/integrationpluginmcp3008.py usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/mcp3008/ +mcp3008/requirements.txt usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/mcp3008/ diff --git a/i2cdevices/integrationplugini2cdevices.json b/i2cdevices/integrationplugini2cdevices.json index 1f2105a3..cb68e09a 100644 --- a/i2cdevices/integrationplugini2cdevices.json +++ b/i2cdevices/integrationplugini2cdevices.json @@ -71,7 +71,7 @@ "id": "d52cd791-6d92-49e4-b46b-593cb0691d46", "name": "channel3", "displayName": "Channel 3", - "displayNameEvent": "Channel 1 value changed", + "displayNameEvent": "Channel 3 value changed", "type": "double", "defaultValue": 0, "minValue": 0, diff --git a/mcp3008/README.md b/mcp3008/README.md new file mode 100644 index 00000000..2ca108c9 --- /dev/null +++ b/mcp3008/README.md @@ -0,0 +1,19 @@ +# MCP3008 + +This plugin adds support for the MCP3008 Analog Digital Converter to nymea. + +> Note: This plugin will only work on the Raspberry Pi + +## Supported Things + +* MCP3008 + +## Requirements + +* The MCP3008 must be wired up properly to the SPI interface. +* The package “nymea-plugin-mcp3008” must be installed + +## More + +[Adafruit tutorial](https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008) +[MCP3008 data sheet](https://cdn-shop.adafruit.com/datasheets/MCP3008.pdf) diff --git a/mcp3008/integrationpluginmcp3008.json b/mcp3008/integrationpluginmcp3008.json new file mode 100644 index 00000000..2c24a619 --- /dev/null +++ b/mcp3008/integrationpluginmcp3008.json @@ -0,0 +1,140 @@ +{ + "name": "mcp3008", + "displayName": "MCP3008", + "id": "c45ff005-b9e9-4334-babb-4c00d6b7b714", + "vendors": [ + { + "name": "microchip", + "displayName": "Microchip Technology", + "id": "6a416628-edfc-4e15-a530-eb7c7fc00ace", + "thingClasses": [ + { + "id": "b22461d4-7961-492d-b2a1-f613abc8a5fb", + "name": "mcp3008", + "displayName": "MCP3008 SPI ADC", + "createMethods": ["User"], + "paramTypes": [ + { + "id": "cf97ede3-429a-4a6c-8eeb-99d7afd2eb37", + "name": "clk", + "displayName": "CLK", + "type": "uint", + "defaultValue": 11 + }, + { + "id": "2e84602a-ef28-4f8c-bd0b-233036155da3", + "name": "miso", + "displayName": "MISO", + "type": "uint", + "defaultValue": 9 + }, + { + "id": "7dc0d2f1-2960-4fdc-b77b-3ab93a33fa6c", + "name": "mosi", + "displayName": "MOSI", + "type": "uint", + "defaultValue": 10 + }, + { + "id": "0771e974-008e-4190-bee7-efb6163decc5", + "name": "cs", + "displayName": "CS", + "type": "uint", + "defaultValue": 8 + } + ], + "stateTypes":[ + { + "id": "1c95c1ca-8043-4096-9e0c-62d6a9bcd468", + "name": "channel1", + "displayName": "Channel 1", + "displayNameEvent": "Channel 1 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "cf354a91-f994-47ef-aeeb-2f03e54f1d7a", + "name": "channel2", + "displayName": "Channel 2", + "displayNameEvent": "Channel 2 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "b0beb2b3-cb44-488f-b0e8-a6f3433b6683", + "name": "channel3", + "displayName": "Channel 3", + "displayNameEvent": "Channel 3 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "c4f1cf54-427a-4b91-aa5e-7256e1b130bf", + "name": "channel4", + "displayName": "Channel 4", + "displayNameEvent": "Channel 4 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "593cdc76-25ec-4252-90ce-8a8da01cf328", + "name": "channel5", + "displayName": "Channel 5", + "displayNameEvent": "Channel 5 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "0e3a1f3d-eb4d-4bd5-bfe8-c1404713039e", + "name": "channel6", + "displayName": "Channel 6", + "displayNameEvent": "Channel 6 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "3d53239b-51ee-4097-8028-291ff063b3ca", + "name": "channel7", + "displayName": "Channel 7", + "displayNameEvent": "Channel 7 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + }, + { + "id": "c10ed6ba-0248-477d-98ad-23d7318b6468", + "name": "channel8", + "displayName": "Channel 8", + "displayNameEvent": "Channel 8 value changed", + "type": "double", + "defaultValue": 0, + "minValue": 0, + "maxValue": 3.3, + "ioType": "analogInput" + } + ] + } + ] + } + ] +} diff --git a/mcp3008/integrationpluginmcp3008.py b/mcp3008/integrationpluginmcp3008.py new file mode 100644 index 00000000..e0016a58 --- /dev/null +++ b/mcp3008/integrationpluginmcp3008.py @@ -0,0 +1,116 @@ +import nymea +import RPi.GPIO as GPIO + +pluginTimer = None + +def readadc(adcnum, clockpin, mosipin, misopin, cspin): + if ((adcnum > 7) or (adcnum < 0)): + return -1 + GPIO.output(cspin, True) + + GPIO.output(clockpin, False) # start clock low + GPIO.output(cspin, False) # bring CS low + + commandout = adcnum + commandout |= 0x18 # start bit + single-ended bit + commandout <<= 3 # we only need to send 5 bits here + for i in range(5): + if (commandout & 0x80): + GPIO.output(mosipin, True) + else: + GPIO.output(mosipin, False) + commandout <<= 1 + GPIO.output(clockpin, True) + GPIO.output(clockpin, False) + + adcout = 0 + # read in one empty bit, one null bit and 10 ADC bits + for i in range(12): + GPIO.output(clockpin, True) + GPIO.output(clockpin, False) + adcout <<= 1 + if (GPIO.input(misopin)): + adcout |= 0x1 + + GPIO.output(cspin, True) + + adcout >>= 1 # first bit is 'null' so drop it + return adcout + + +def setupThing(info): + logger.log("SetupThing for MCP3008:", info.thing.name) + + SPICLK = info.thing.paramValue(mcp3008ThingClkParamTypeId) + SPIMISO = info.thing.paramValue(mcp3008ThingMisoParamTypeId) + SPIMOSI = info.thing.paramValue(mcp3008ThingMosiParamTypeId) + SPICS = info.thing.paramValue(mcp3008ThingCsParamTypeId) + + try: + GPIO.setwarnings(False) + GPIO.setmode(GPIO.BCM) #to specify whilch pin numbering system + # set up the SPI interface pins + GPIO.setup(SPIMOSI, GPIO.OUT) + GPIO.setup(SPIMISO, GPIO.IN) + GPIO.setup(SPICLK, GPIO.OUT) + GPIO.setup(SPICS, GPIO.OUT) + + logger.log("Reading channel 0") + chan0Value = readadc(0, SPICLK, SPIMOSI, SPIMISO, SPICS) + logger.log('Raw ADC Value: %s' % chan0Value) + logger.log('ADC Voltage: %sV' % str("%.2f"%((chan0Value/1024.)*5))) + + except Exception as e: + logger.warn("Unable to open SPI port", str(e)) + info.finish(nymea.ThingErrorHardwareFailure, "Unable to connect to the device. Please verify it is connected properly to the SPI interface.") + return + + info.finish(nymea.ThingErrorNoError) + + global pluginTimer + if pluginTimer is None: + pluginTimer = nymea.PluginTimer(5, pollChannels) + + + +def thingRemoved(thing): + del devices[thing] + if len(myThings()) == 0: + GPIO.cleanup() + + + +def pollChannels(): + for thing in myThings(): + SPICLK = thing.paramValue(mcp3008ThingClkParamTypeId) + SPIMISO = thing.paramValue(mcp3008ThingMisoParamTypeId) + SPIMOSI = thing.paramValue(mcp3008ThingMosiParamTypeId) + SPICS = thing.paramValue(mcp3008ThingCsParamTypeId) + + try: + chan0Value = readadc(0, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel1StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(1, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel2StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(2, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel3StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(3, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel4StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(4, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel5StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(5, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel6StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(6, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel7StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + chan0Value = readadc(7, SPICLK, SPIMOSI, SPIMISO, SPICS) + thing.setStateValue(mcp3008Channel8StateTypeId, str("%.2f"%((chan0Value/1024.)*5))) + + except: + logger.warn("Failed to read values from", thing.name) diff --git a/mcp3008/mcp3008.pro b/mcp3008/mcp3008.pro new file mode 100644 index 00000000..0664fd88 --- /dev/null +++ b/mcp3008/mcp3008.pro @@ -0,0 +1,6 @@ +TEMPLATE = aux + +OTHER_FILES = integrationpluginmcp3008.json \ + integrationpluginmcp3008.py \ + MCP3008.py + diff --git a/mcp3008/meta.json b/mcp3008/meta.json new file mode 100644 index 00000000..e4cb2fe8 --- /dev/null +++ b/mcp3008/meta.json @@ -0,0 +1,13 @@ +{ + "title": "MCP3008", + "tagline": "Connect MCP3008 digital analog converters to nymea.", + "icon": "", + "stability": "consumer", + "offline": false, + "technologies": [ + "SPI" + ], + "categories": [ + "DYI" + ] +} diff --git a/mcp3008/requirements.txt b/mcp3008/requirements.txt new file mode 100644 index 00000000..4a8212a7 --- /dev/null +++ b/mcp3008/requirements.txt @@ -0,0 +1,3 @@ +RPi.GPIO==0.7.0 \ + --hash=sha256:7424bc6c205466764f30f666c18187a0824077daf20b295c42f08aea2cb87d3f \ + --hash=sha256:6a4791f41cafc2ee6e4cb70e5bd31fadc66a0cfab29b38df8723a98f6f73ad5a diff --git a/nymea-plugins.pro b/nymea-plugins.pro index eb04e664..dae957e7 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -39,6 +39,7 @@ PLUGIN_DIRS = \ netatmo \ networkdetector \ nuki \ + mcp3008 \ onewire \ openuv \ openweathermap \