-
Notifications
You must be signed in to change notification settings - Fork 0
Arduino Service and Endpoint #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
bf7ed95
316dd1c
34a42cf
0f96e84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| #include <DallasTemperature.h> | ||
| #include <EtherCard.h> | ||
| #include <IPAddress.h> | ||
| #include <OneWire.h> | ||
| #include <string.h> | ||
|
|
||
| /* ----------------------------- CONFIGURATION ----------------------------*/ | ||
|
|
||
| #define DEBUG 1 // 0 disabled, 1 enabled | ||
| #define DHCP 1 // 0 disabled, 1 enabled | ||
| #define MAC 0 // 0 for astro-yacht, 1 for astro-zabra, 2 for astro-aye | ||
|
|
||
| /* ------------------------------------------------------------------------*/ | ||
|
|
||
| #if DEBUG | ||
| #define DEBUG_PRINTLN(x) Serial.println(x) | ||
| #define DEBUG_PRINT(x) Serial.print(x) | ||
| #define DEBUG_PRINTIP(x, y) ether.printIp(x, y) | ||
| #else | ||
| #define DEBUG_PRINTLN(x) | ||
| #define DEBUG_PRINT(x) | ||
| #define DEBUG_PRINTIP(x) | ||
| #endif | ||
|
|
||
| const byte pinFlowmeter = 3; // Interrupt pin (D2) | ||
| const byte pinTemperature = 5; // Digital pin (D6) | ||
| const byte pinPressure = 6; | ||
|
|
||
| volatile unsigned int pulseCount = 0; // Stores number of pulses | ||
| const int flowMeasurementDuration = 500; // Microsecond | ||
|
|
||
| OneWire oneWire(pinTemperature); | ||
| DallasTemperature temperatureSensor(&oneWire); | ||
|
|
||
| #if MAC == 0 | ||
| static byte mymac[] = {0x5a, 0x09, 0xb5, 0xaf, 0xd8, 0x11}; | ||
| #elif MAC == 1 | ||
| static byte mymac[] = {0x5a, 0x09, 0xb5, 0xaf, 0xd8, 0x10}; | ||
| #elif MAC == 2 | ||
| static byte mymac[] = {0x5a, 0x09, 0xb5, 0xaf, 0xd8, 0x09}; | ||
| #endif | ||
|
|
||
| #if DHCP == 0 | ||
| static byte myip[] = {192, 168, 1, 10}; | ||
| // Gateway IP address | ||
| static byte gwip[] = {192, 168, 0, 10}; | ||
| #endif | ||
|
|
||
| byte Ethernet::buffer[500]; // TCP/IP send and receive buffer | ||
|
|
||
| void pulseISR() { | ||
| pulseCount++; // Interrupt service routine: Increment the pulse count on each interrupt | ||
| } | ||
|
|
||
| float getTemperatureC() { | ||
| /* Returns temperature in degrees Celcius */ | ||
| temperatureSensor.requestTemperatures(); | ||
| return temperatureSensor.getTempCByIndex(0); | ||
| } | ||
|
|
||
| float getPressureV() { | ||
| /* Returns the pressure in Volts */ | ||
| int sensorValue = analogRead(pinPressure); | ||
| return sensorValue * (5.0 / 1023.0); | ||
| } | ||
|
|
||
| float getFlowHz() { | ||
| /* Returns pulse frequency in Herz */ | ||
| pulseCount = 0; | ||
| delay(flowMeasurementDuration); | ||
| float frequency = (float)pulseCount * 1000.0 / (flowMeasurementDuration); | ||
| return frequency; | ||
| } | ||
|
|
||
| void setup() { | ||
| #if DEBUG | ||
| Serial.begin(115200); | ||
| #endif | ||
|
|
||
| pinMode(pinFlowmeter, INPUT_PULLUP); | ||
| attachInterrupt(digitalPinToInterrupt(pinFlowmeter), pulseISR, RISING); | ||
|
|
||
| temperatureSensor.begin(); | ||
| if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) { | ||
| DEBUG_PRINTLN("Failed to access Ethernet controller."); | ||
| } else { | ||
| DEBUG_PRINTLN("Ethernet controller sucessfully accessed."); | ||
| } | ||
|
|
||
| #if DHCP == 0 | ||
| ether.staticSetup(myip, gwip); | ||
| #else | ||
| if (!ether.dhcpSetup()) { | ||
| DEBUG_PRINTLN("DHCP failed"); | ||
| } else { | ||
| DEBUG_PRINTIP("DHCP request successful. IP-Adress: ", ether.myip); | ||
| DEBUG_PRINT("Lease time: "); | ||
| DEBUG_PRINTLN(ether.getLeaseTime()); | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| void loop() { | ||
| word len = ether.packetReceive(); | ||
| word pos = ether.packetLoop(len); | ||
|
|
||
| if (pos) { | ||
| char *data = (char *)Ethernet::buffer + pos; | ||
| DEBUG_PRINT("Received data: "); | ||
| DEBUG_PRINTLN(data); | ||
|
|
||
| float sensorData = 0; | ||
| boolean caseFound = false; | ||
|
|
||
| char response[10]; | ||
| memset(response, '\n', sizeof(response)); | ||
| if (strncmp(data, "temp?", 5) == 0) { | ||
| sensorData = getTemperatureC(); | ||
| dtostrf(sensorData, 4, 4, response); | ||
| } else if (strncmp(data, "pres?", 5) == 0) { | ||
| sensorData = getPressureV(); | ||
| dtostrf(sensorData, 4, 4, response); | ||
| } else if (strncmp(data, "flow?", 5) == 0) { | ||
| sensorData = getFlowHz(); | ||
| dtostrf(sensorData, 4, 4, response); | ||
| } else if (strncmp(data, "test?", 5) == 0) { | ||
| strcpy(response, "success"); | ||
| } else { | ||
| strcpy(response, "invalid"); | ||
| } | ||
|
|
||
| DEBUG_PRINT("Sending data: "); | ||
| DEBUG_PRINTLN(response); | ||
|
|
||
| memcpy(ether.tcpOffset(), response, sizeof(response)); | ||
| ether.httpServerReply(sizeof(response) - 1); | ||
| } | ||
|
|
||
| // if DHCP lease is expired or millis wrap over after 49 days | ||
| #if DHCP | ||
| float leaseStart = ether.getLeaseStart(); | ||
| if ((millis() - leaseStart >= ether.getLeaseTime()) || | ||
| (millis() < leaseStart)) { | ||
| if (!ether.dhcpSetup()) { | ||
| DEBUG_PRINTLN("DHCP renew failed"); | ||
| delay(60000); // wait for one minute before retrying. | ||
| } else { | ||
| DEBUG_PRINTIP("DHCP renewed. IP-Adress: ", ether.myip); | ||
| DEBUG_PRINT("Lease time: "); | ||
| DEBUG_PRINTLN(ether.getLeaseTime()); | ||
| } | ||
| } | ||
| #endif | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| from dripline.core import Entity, calibrate, ThrowReply | ||
|
|
||
| import logging | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| __all__ = [] | ||
| __all__.append("ArduinoGetEntity") | ||
|
|
||
| class ArduinoGetEntity(Entity): | ||
| def __init__(self, | ||
| get_str=None, | ||
| **kwargs): | ||
|
|
||
| Entity.__init__(self, **kwargs) | ||
| self.get_str = get_str | ||
|
|
||
| @calibrate() | ||
| def on_get(self): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it useful to include a logger.debug of the response? like: |
||
| return self.service.send_to_device([self.get_str]) | ||
|
|
||
| def on_set(self, value): | ||
| raise ThrowReply('message_error_invalid_method', f"endpoint '{self.name}' does not support set") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import re | ||
| import socket | ||
| import threading | ||
|
|
||
| from dripline.core import Service | ||
|
|
||
| import logging | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| __all__ = [] | ||
| __all__.append('EthernetArduinoService') | ||
|
|
||
| class EthernetArduinoService(Service): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add docstring? |
||
| def __init__(self, | ||
| socket_info, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. given the odd form of socket_info, probably best to define the init args (I know they follow a reduced set of EthernetSCPIService, but since they are independently defined, the docs don't follow) |
||
| socket_timeout=10, | ||
| **kwargs | ||
| ): | ||
|
|
||
| Service.__init__(self, **kwargs) | ||
|
|
||
| if isinstance(socket_info, str): | ||
| logger.debug(f"Formatting socket_info: {socket_info}") | ||
| re_str = "\([\"'](\S+)[\"'], ?(\d+)\)" | ||
| (ip,port) = re.findall(re_str,socket_info)[0] | ||
| socket_info = (ip,int(port)) | ||
|
|
||
| self.alock = threading.Lock() | ||
| self.socket = socket.socket() | ||
|
|
||
| self.socket_timeout = float(socket_timeout) | ||
| self.socket_info = socket_info | ||
|
|
||
|
|
||
| def send_to_device(self, commands, **kwargs): | ||
|
|
||
| if isinstance(commands, str): | ||
| commands = [commands] | ||
|
|
||
| self.alock.acquire() | ||
| data = [] | ||
| try: | ||
| data = self._send_commands(commands) | ||
| except Exception as err: | ||
| logger.critical(str(err)) | ||
| finally: | ||
| self.alock.release() | ||
| to_return = ';'.join(data) | ||
| logger.debug(f"should return:\n{to_return}") | ||
| return to_return | ||
|
|
||
|
|
||
| def _send_commands(self, commands): | ||
| all_data=[] | ||
|
|
||
| for command in commands: | ||
| try: | ||
| self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| self.socket.settimeout(self.socket_timeout) | ||
| self.socket.connect(self.socket_info) | ||
| self.socket.sendall(bytes(command, encoding="utf-8")) | ||
| data = self.socket.recv(1024) | ||
| data = data.rstrip(b'\x00\n').decode("utf-8") | ||
| except Exception as err: | ||
| logger.warning(f"While socket communication we received an error: {err}") | ||
| finally: | ||
| self.socket.close() | ||
|
|
||
| logger.info(f"Received: {data}") | ||
|
|
||
| all_data.append(data) | ||
|
|
||
| return all_data | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should establish convention for where example Arduino sketches go. Essentially this is templating a SCPI-lite device, which isn't really a dripline/dragonfly code element.
In Phase II we stashed them in the hardware repo, which was just an ad hoc solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We move all our arduino code and raspberryPi code here: https://github.com/project8/MainzDripline3/tree/main/MicroControllerScripts
The main purpose is to have it version controlled and backed up.
In this pull request I wanted to give it as an example and thus it may better be in the example folder