diff --git a/arduino/actuator/actuator.ino b/arduino/actuator/actuator.ino new file mode 100644 index 0000000..e4e5628 --- /dev/null +++ b/arduino/actuator/actuator.ino @@ -0,0 +1,132 @@ +#include +#include + +#ifndef cbi +#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) +#endif + +// set I2C address +const int I2C_ADDR = 0x8; + +// configure pin addresses +const int LED_PIN = LED_BUILTIN; +const int MECC_LED_PIN = 3; + +// initialize MeccaBrain +MeccaBrain meccLedChain(MECC_LED_PIN); +const int MECC_COMM_LOOPS = 20; + +// the setup function runs once when you press reset or power the board +void setup() { + // initialize digital pin LED_BUILTIN as an output. + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // initialize Meccano LED pin as an output + pinMode(MECC_LED_PIN, OUTPUT); + + Wire.begin(I2C_ADDR); + + #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__) + // deactivate internal pull-ups for twi + // as per note from atmega8 manual pg167 + cbi(PORTC, 4); + cbi(PORTC, 5); + #else + // deactivate internal pull-ups for twi + // as per note from atmega128 manual pg204 + cbi(PORTD, 0); + cbi(PORTD, 1); + #endif + + Wire.onReceive(receiveEvent); // register event handler + + Serial.begin(9600); + + // discover Meccano modules + for (int i = 0; i < MECC_COMM_LOOPS; i++) + { + meccLedChain.communicate(); + } + + delay(2000); +} + +// the loop function runs over and over again forever +void loop() { + // NOP +} + +// action received for Meccano LED +void actionMeccLed(int setting) { + byte red, green, blue; + byte fadeTime = 0x0; + if (setting == 0) { + red = 0x0; green = 0x0; blue = 0x0; + } else if (setting == 1) { + red = 0x7; green = 0x0; blue = 0x0; + } else if (setting == 2) { + red = 0x0; green = 0x7; blue = 0x0; + } else if (setting == 3) { + red = 0x0; green = 0x0; blue = 0x7; + } else if (setting == 4) { + red = 0x7; green = 0x7; blue = 0x0; + } else if (setting == 5) { + red = 0x0; green = 0x7; blue = 0x7; + } else if (setting == 6) { + red = 0x7; green = 0x0; blue = 0x7; + } else if (setting == 7) { + red = 0x7; green = 0x7; blue = 0x7; + } else { + red = 0x0; green = 0x0; blue = 0x0; + } + setMeccLedColor(red, green, blue, fadeTime); +} + +// set the color of Meccano LED. +// red, green and blue are from 0 to 7 (0 - no color, 7 - max color) +// fadeTime is from 0 to 7 and means the speed of color change (0 - immediate change, 7 - longest change) +// example: setMeccLedColor(7, 0, 0, 0) means change color to red immediately +void setMeccLedColor(byte red, byte green, byte blue, byte fadeTime) +{ + meccLedChain.setLEDColor(red, green, blue, fadeTime); + for (int i = 0; i < MECC_COMM_LOOPS; i++) + { + meccLedChain.communicate(); + } +} + +// action received for test (internal Ardino) LED +void actionTestLed(char setting) { + if (setting == 0) { + digitalWrite(LED_PIN, 0); + } else { + digitalWrite(LED_PIN, 1); + } +} + +// process received event +void processEvent(char type, char id, char setting) { + if (type == 'L') { + actionTestLed(setting); + } else if (type == 'M') { + actionMeccLed(setting); + } else if (type == 'S') { + // NOP + } +} + +// executes when data is received from I2C master +// registered as an event handler; see setup() +void receiveEvent(int howMany) { + while (Wire.available()) { // loop through all but the last + if (Wire.available() == 3) { + char type = Wire.read(); // receive byte as a character + char id = Wire.read(); // receive byte as a character + char setting = Wire.read(); // receive byte as a character + processEvent(type, id, setting); + } else { + char c = Wire.read(); // receive byte as a character + } + } +} diff --git a/raspi/control.py b/raspi/control.py new file mode 100644 index 0000000..466bf09 --- /dev/null +++ b/raspi/control.py @@ -0,0 +1,63 @@ +from collections import deque +import csv +import argparse +import time + +import utils.transform as transform +import utils.i2c as i2c +from objects.anim_action import AnimAction + +COL_TIME_STR = 0 +COL_DEVICE_TYPE = 1 +COL_DEVICE_ID = 2 +COL_SETTING = 3 + +anim_actions = deque([]) + +def read_anim_csv(filename, delimiter=',', quotechar='"'): + csv_file = open(filename, 'r') + csv_reader = csv.reader(csv_file, delimiter=delimiter, quotechar=quotechar) + + for row in csv_reader: + time_str = str(row[COL_TIME_STR]) + time_sec = transform.time_convert(time_str) + device_type = str(row[COL_DEVICE_TYPE]) + device_id = int(row[COL_DEVICE_ID]) + setting = int(row[COL_SETTING]) + + action = AnimAction(time_str, time_sec, device_type, device_id, setting) + anim_actions.append(action) + + csv_file.close() + +def handle_anim(): + start = time.time() + end = time.time() + while len(anim_actions) > 0: + if anim_actions[0].time_sec <= end - start: + handle_action(anim_actions.popleft()) + time.sleep(0.001) + end = time.time() + +def handle_action(action): + i2c.write_anim_i2c(action) + print(action) + +def main(): + parser = argparse.ArgumentParser(description="Animatronic Control Program") + parser.add_argument('-i', '--input', type=str, required=True) + args = parser.parse_args() + + input_file = args.input + print('Input file: ' + input_file) + if '.csv' in input_file.lower(): + print('Loading file...') + read_anim_csv(input_file) + print('Loaded!') + wait = input('Press enter to continue...') + handle_anim() + else: + raise Exception('Invalid file type') + +if __name__ == '__main__': + main() diff --git a/raspi/objects/__init__.py b/raspi/objects/__init__.py new file mode 100644 index 0000000..139597f --- /dev/null +++ b/raspi/objects/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/raspi/objects/anim_action.py b/raspi/objects/anim_action.py new file mode 100644 index 0000000..54c0ab7 --- /dev/null +++ b/raspi/objects/anim_action.py @@ -0,0 +1,12 @@ + +class AnimAction: + def __init__(self, time_str, time_sec, device_type, device_id, setting): + self.time_str = time_str + self.time_sec = time_sec + self.device_type = device_type + self.device_id = device_id + self.setting = setting + + def __str__(self): + return str(self.time_str) + '-' + str(self.device_type) + '-' + str(self.device_id) + '-' + str(self.setting) + \ No newline at end of file diff --git a/raspi/test.csv b/raspi/test.csv new file mode 100644 index 0000000..87e3cc6 --- /dev/null +++ b/raspi/test.csv @@ -0,0 +1,13 @@ +00:00:01.10,LED,1,1 +00:00:01.20,LED,1,0 +00:00:01.30,LED,1,1 +00:00:01.40,LED,1,0 +00:00:03.00,MLED,1,0 +00:00:05.00,MLED,1,1 +00:00:07.00,MLED,1,2 +00:00:09.00,MLED,1,3 +00:00:11.00,MLED,1,4 +00:00:13.00,MLED,1,5 +00:00:15.00,MLED,1,6 +00:00:17.00,MLED,1,7 +00:00:20.00,SVO,1,90 \ No newline at end of file diff --git a/raspi/utils/__init__.py b/raspi/utils/__init__.py new file mode 100644 index 0000000..139597f --- /dev/null +++ b/raspi/utils/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/raspi/utils/i2c.py b/raspi/utils/i2c.py new file mode 100644 index 0000000..9e8f02f --- /dev/null +++ b/raspi/utils/i2c.py @@ -0,0 +1,15 @@ +from smbus import SMBus + +from objects.anim_action import AnimAction + +I2C_ADDR = 0x8 # bus address +I2C_BUS = SMBus(1) # indicates /dev/ic2-1 + + + +def write_anim_i2c(action): + data = [action.device_type.encode()[0], action.device_id, action.setting] + I2C_BUS.write_i2c_block_data(I2C_ADDR, 0x0, data) + #I2C_BUS.write_byte(I2C_ADDR, action.device_type.encode()[0]) + #I2C_BUS.write_byte(I2C_ADDR, action.device_id) + #I2C_BUS.write_byte(I2C_ADDR, action.setting) \ No newline at end of file diff --git a/raspi/utils/transform.py b/raspi/utils/transform.py new file mode 100644 index 0000000..0da61e5 --- /dev/null +++ b/raspi/utils/transform.py @@ -0,0 +1,4 @@ + +def time_convert(time_str): + time_list = time_str.split(':') + return float(int(time_list[0]) * 3600 + int(time_list[1]) * 60 + float(time_list[2]))