CONCEPT:
Masks Off is a project that investigates the proxy face or, in other words, the face upon the face. The masks that are constructed as a result of this investigation juxtapose the abstracted face of the wearer’s partner on top of that of the wearer and the partner’s environment upon the wearer. The mask takes data from the partner’s environment in order to activate the masks. In the case of Elise’s mask, the position of Anishwar’s head while wearing his mask sends data to Elise’s mask in order to activate the pneumatic and speaker of the Bluefruit. Anishwar’s mask is activated by the level of darkness in Elise’s environment which causes the pneumatic in his mask to inflate and the speaker on his Bluefruit to play tones.
The team decided to explore the concept of the proxy face and the juxtaposition of distant people and environments on another because they were interested in analyzing the significance and implications of the remote digital communication that has become popularized during the Covid-19 pandemic through the symbol of the mask which has taken on a new prominence and significance during this time period.
EXECUTION:
We decided to overlap tulle over fabric since the embroidery was much easier on the tulle. Elise stitched the designs into the tulle to create the faces and attached the tulle to the fabric underneath. For Anishwar, we used the lace ‘sheet’ that Elise sewed together. For Elise, we used the stretchy metallic fabric. The masks were pretty jarring in imagery and interesting at the same time.
PROCESS:
Our process begin with experimenting on what we wanted the masks to incorporate and how might they look. Anishwar drew sketches of ideas and the one below was what we ultimately went with. The idea was to create a proxy face on top of our own and involve lights and noise when reacting to the other’s environment. Our final tweaked those processes.
The idea for the masks came from the runway, looking at the work of John Galliano. You can see the influence in our work as we used tulle and bold colors as well as the black thread detailing in Anishwar’s mask. Elise’s mask also used embroidery detailing and tulle but in a red thread.
After gathering the basic idea, we chose to look at the working parts. We experimented with pneumatics before we got the motor pump. Once we got the motor, we also made sure to add holes to that the inflatables could relax between pumping cycles. The final inflatables were smaller round bladders.
We also experimented with the design aspects, looking at embroidery and the fabric that we could use. When making the final mask, the embroidery foot on the sewing machine was eating up the tulle and making holes, so we chose to do hand-stitching with embroidery floss when making the faces.
After we figured out our working parts, we combined our efforts to create the final masks. We had a lot of trouble getting the code to work over the bridge and that took a lot of our time. The final masks also used a double layer of fabric, one with embroidery and the other to give more structure and color. The pneumatics were placed under the masks, either with elastic around the head or attached to the inside of the mask.
ANISHWAR’S CODE:
# cpb_pump_control.py # Demonstrate control of an air pump based on a DC motor driven via a MOSFET # transistor driver circuit *or* a DRV8833 motor driver in a single-ended mode. # # 1. The driver PWM input connects to A2 # 2. The battery ground line connects to GND # Related documentation: # https://circuitpython.readthedocs.io/en/latest/shared-bindings/pwmio/index.html#module-pwmio # https://circuitpython.readthedocs.io/projects/motor/en/latest/api.html#module-adafruit_motor.servo # ---------------------------------------------------------------- # Import standard Python modules. import time, math, sys # Import the board-specific input/output library. from adafruit_circuitplayground import cp # Import the low-level hardware libraries. import board import pwmio import supervisor # ---------------------------------------------------------------- # Initialize hardware. # Create a PWMOut object on pad A2 to generate control signals. pwm = pwmio.PWMOut(board.A3, duty_cycle=0, frequency=20000) # ---------------------------------------------------------------- # Initialize global variables for the main loop. phase_angle = 0.0 cycle_duration = 12 # seconds per cycle phase_rate = 2*math.pi / cycle_duration # radians/second next_pwm_update = time.monotonic_ns() next_status_update = time.monotonic_ns() toneCheck = True nextSound = time.monotonic_ns() pitch = 329 pitchTwo = 293 nextPrint = time.monotonic_ns() tokens = [] motionDuration = 0 soundDuration = 0 soundDurationTwo = 0 lastNow = time.monotonic_ns() # ---------------------------------------------------------------- # Enter the main event loop. while True: # Read the current integer clock. #print (pitch) now = time.monotonic_ns() soundNow = time.monotonic_ns() elapsedTime = now - lastNow lastNow = now x,y,z = cp.acceleration if now >= nextPrint: print(pwm.duty_cycle) nextPrint = now + 2000000000 if x > 1 or y < 0: print("start") if x >= 1.25: print("go") elif y < 0: print("onward") if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() tokens = line.split() if line.startswith("begi"): motionDuration = 7050000000 if line.startswith("now"): soundDuration = 500000000 if line.startswith("geronimo"): soundDurationTwo = 500000000 # If the time has arrived to update the servo command signal: if soundDuration > soundDurationTwo: soundDurationTwo = 0 elif soundDurationTwo > soundDuration: soundDuration = 0 if motionDuration > 0: if now >= next_pwm_update: next_pwm_update += 20000000 # 20 msec in nanoseconds (50 Hz update) pwm_level = 0.5 + 0.5 * math.sin(phase_angle) # convert a unit-range (0 to 1) pwm_level to a 16-bit integer representing a fraction new_duty_cycle = min(max(int(pwm_level * 2**16), 0), 2**16-1) # If the new value is less than a reasonable minimum, clamp to zero. # The pump motor will stall if the PWM fraction is too low; this turns # it off instead. if new_duty_cycle < 48000: pwm.duty_cycle = 0 else: pwm.duty_cycle = new_duty_cycle phase_angle = (phase_angle + phase_rate * 0.01) % (.5 * math.pi) # If either button is pressed, override the generated servo signal and run the motor. if cp.button_a or cp.button_b: pwm.duty_cycle = 2**16-1 # If the time has arrived to display the status: if now >= next_status_update: next_status_update += 500000000 # 0.5 sec in nanoseconds (2 Hz update) motionDuration -= elapsedTime else: pwm.duty_cycle = 0 if soundDuration > 0: if soundNow >= nextSound: if pitch == 329: pitch = 493 cp.stop_tone() cp.start_tone(pitch) nextSound = soundNow + 500000000 elif pitch == 493: pitch = 329 cp.stop_tone() cp.start_tone(pitch) nextSound = soundNow + 500000000 soundDuration -= elapsedTime elif soundDurationTwo > 0: if soundNow >= nextSound: if pitchTwo == 293: pitchTwo = 466 cp.stop_tone() cp.start_tone(pitchTwo) nextSound = soundNow + 500000000 elif pitchTwo == 466: pitchTwo = 293 cp.stop_tone() cp.start_tone(pitchTwo) nextSound = soundNow + 500000000 soundDurationTwo -= elapsedTime else: cp.stop_tone()
ELISE’S CODE:
# https://circuitpython.readthedocs.io/en/latest/shared-bindings/pwmio/index.html#module-pwmio # https://circuitpython.readthedocs.io/projects/motor/en/latest/api.html#module-adafruit_motor.servo import time, math, sys from adafruit_circuitplayground import cp import board import pwmio import supervisor pwm = pwmio.PWMOut(board.A3, duty_cycle=0, frequency=20000) phase_angle = 0.0 cycle_duration = 12 # seconds per cycle phase_rate = 2*math.pi / cycle_duration # radians/second next_pwm_update = time.monotonic_ns() next_status_update = time.monotonic_ns() toneCheck = True nextSound = time.monotonic_ns() pitch = 329 pitchTwo = 293 nextPrint = time.monotonic_ns() motionDuration = 0 soundDuration = 0 soundDurationTwo = 0 lastnow = time.monotonic_ns() while True: # Read the current integer clock. #print(cp.light) now = time.monotonic_ns() soundNow = time.monotonic_ns() elapsed_time = now - lastnow lastnow = now if now >= nextPrint: nextPrint = now + 2000000000 if cp.light < 100: print("begi") if cp.light < 81 and cp.light >= 21: print("now") elif cp.light < 20: print("geronimo") else: print("bright") if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() #print(line) #tokens = line.split() if line.startswith("start"): motionDuration = 9000000000 if line.startswith("go"): soundDuration = 5000000000 if line.startswith("onward"): soundDurationTwo = 5000000000 # If the time has arrived to update the servo command signal: if motionDuration > 0: if now >= next_pwm_update: next_pwm_update += 20000000 # 20 msec in nanoseconds (50 Hz update) pwm_level = 0.5 + 0.5 * math.sin(phase_angle) # convert a unit-range (0 to 1) pwm_level to a 16-bit integer representing a fraction new_duty_cycle = min(max(int(pwm_level * 2**16), 0), 2**16-1) # If the new value is less than a reasonable minimum, clamp to zero. # The pump motor will stall if the PWM fraction is too low; this turns # it off instead. if new_duty_cycle < 48000: pwm.duty_cycle = 0 else: pwm.duty_cycle = new_duty_cycle phase_angle = (phase_angle + phase_rate * 0.020) % (1 * math.pi) # If either button is pressed, override the generated servo signal and run the motor. if cp.button_a or cp.button_b: pwm.duty_cycle = 2**16-1 # If the time has arrived to display the status: if now >= next_status_update: next_status_update += 500000000 # 0.5 sec in nanoseconds (2 Hz update) #print(pwm.duty_cycle) motionDuration -= elapsed_time else: pwm.duty_cycle = 0 cp.stop_tone() if soundDuration > soundDurationTwo: soundDurationTwo = 0 elif soundDurationTwo > soundDuration: soundDuration = 0 if soundDuration > 0 and soundDurationTwo == 0: if soundNow >= nextSound: if pitch == 329: pitch = 493 cp.stop_tone() cp.start_tone(pitch) nextSound = soundNow + 500000000 elif pitch == 493: pitch = 329 cp.stop_tone() cp.start_tone(pitch) nextSound = soundNow + 500000000 soundDuration -= elapsed_time elif soundDurationTwo > 0 and soundDuration == 0: if soundNow >= nextSound: if pitchTwo == 293: pitchTwo = 466 cp.stop_tone() cp.start_tone(pitchTwo) nextSound = soundNow + 500000000 elif pitchTwo == 466: pitchTwo = 293 cp.stop_tone() cp.start_tone(pitchTwo) nextSound = soundNow + 500000000 soundDurationTwo -= elapsed_time else: cp.stop_tone()
REFLECTION:
If there was more time, the team would have liked to make more masks and potentially create an installation with multiple of these masks being animated and producing sound together. It would have been interesting to explore this current iteration of the project in different contexts. The primary location for testing the final version of this project was the College of Fine Arts building; however, it would be interesting to see how the masks would be affected if used outside or in vastly more distant locations.
]]>Today’s class includes the remaining three project presentations.
Each remaining project group will present their work for critique.
With the rise of AAPI hate in the past year, we set out to create defensive vests to start a conversation on what it means to feel unsafe as Asian American women. Our vests remind us of the precautions and protections that women take to feel safe. However, we also add our identity as Asian Americans to the purpose of the vest to highlight our vulnerability caused by race tensions in our nation.
Our vests are customized to represent our needs and defensive strategies in times of distress. However, they both retain two key components: one part of the vest is inflated with a hand pump and one part of the vest is inflated with a motor pump.
When the user presses their hand pump to inflate their vest, a signal is sent via a soft touch sensor to inflate their partner’s vest using their motor pump. The vest allows the user to seek physical comfort by squeezing a hand pump. It also allows the user to communicate their emotional state to their partner. Their partner’s vest flies into a protective mode, almost like a call to action or like someone rushing to rescue.
Helen’s Vest
Exposed Organs
My vest is an abstract quilt of our major organs, with many individual pieces filled with different fabrics and stitched together. Besides the heart organ and the collar, which are inflated using the hand pump and motor pump, the other organs are inflated to varying degrees. The more vital the organ, the more inflated it is.
Controlling transparency of vest with different shirts
I paired my inflatable vest with a black long-sleeved shirt and a neutral-colored tank top to see how different bases can bring out different elements of the vest.
Lace
The infill in the vest is mainly pink, blue, and gray lace, with hints of sequins and glitter. Some organs support a variety of colors to represent its function, oxygen level, and value. The lace confetti is incased in the vest, juxtaposing both the delicacy of the lace with the protective purpose of the vest.
Collar
The exposed lace on the collar is reminiscent of a lion’s mane. The raw edges of the lace border creates a delicate yet prickly effect. It is both beautiful and unsettling, as the lace tassels flare out of the collar.
Rebecca’s Vest
In its resting state, my vest is boxy and asymmetrical. I created coral-like edges around the neckpiece; the ridges droop like wings on the shoulders. Brackets and lines coat the body of the vest.
While experimenting with patterns, I explored one-piece construction — having one body and vacuum as opposed to disassembled parts. The style mimicked a life vest and was visually consistent with the clear tubing I used to attach to the motors. I ended up using two pieces: one spanned from the entire left side to the right shoulder, and the other piece was a linear pouch motor to be actuated by the motor pump.
Heart Pump: sending signals
The heart pump has a soft sensor attached to it, so each squeeze is detected. The air itself is pumped into a compartment that is closed off and separated from the rest of the vest. It is coated with one layer of white paint to distinguish it from the other panels of the vest, and it is marked with the phrase “HELP OTW”.
Motor Pump: receiving signals
I used a layer of white paint to create a frosted effect on the part of the vest that is actuated by the motor so the vinyl wouldn’t be as revealing.
Reflection
As someone who likes to slowly refine my work as I progress, I felt exposed while working with a transparent medium. I addressed this by painting some areas of the vest and drawing patterns on both sides. I also appreciated Helen’s use of filling to beautify and also conceal some elements of her vest. Vinyl offered a lot of opportunities to play around with linework and edges, and I had a lot of fun with that.
I can now say I “used telematics to physically actuate and inflate a wearable,” which is an impressive sentence. It actually was really exciting to watch something on your body move at someone else’s signal, especially when that person is a distance away from you. It was also reaffirming to send a (homemade, on top of everything else) help signal and knowing a familiar person was at the other end of it. However, if Helen were to actually be in danger, I would feel helpless watching my vest slowly ascend to reveal some safety tools. If we had more time, Helen and I could refine this idea to make it more functional.
Given more time, we would explore adding more vests to this line, such that when one person is in distress, all the vests fly into action. The vests are meant to represent the need for safety when someone in our communities are distresses. In a sense, we are gearing up to protect our fellow community members as well as feeling their sense of danger and being affected by their distress as well.
""" Project 2: Defensive Vests Helen Yu (heleny1), Rebecca Kim Written by Helen Yu Last Updated: 5/6/21 Summary: When you clench your pump and inflate your vest, your partner's vest leaps into defense mode. A hobby servo helps control the defense mechanism, be it spikes, projectiles, webs, etc. Inputs: Soft Touch Sensor on A1 Outputs: Motor Pump on A2 """ # ---------------------------------------------------------------- # Import any needed standard Python modules. import time, math, sys # Import the board-specific input/output library. from adafruit_circuitplayground import cp # Import the low-level hardware libraries. import board import digitalio import analogio import pwmio # Import the Adafruit helper library. from adafruit_motor import servo # Import the runtime for checking serial port status. import supervisor # ---------------------------------------------------------------- # Initialize hardware. # Configure the digital input pin used for its pullup resistor bias voltage. bias = digitalio.DigitalInOut(board.D10) # pad A3 bias.switch_to_input(pull=digitalio.Pull.UP) # Configure the analog input pin used to measure the sensor voltage. sensor = analogio.AnalogIn(board.A1) scaling = sensor.reference_voltage / (2**16) # Create a PWMOut object (motor pump) on pad A2 to generate control signals. pwm = pwmio.PWMOut(board.A2, duty_cycle=0, frequency=2000) # ---------------------------------------------------------------- # Initialize global variables for the main loop. remote_touch = [0] # Measure the time since the last remote move message, and reset after a period without data. remote_touch_timer = False # Convenient time constant expressed in nanoseconds. second = 1000000000 # Integer time stamp for the next console output. sensing_timer = time.monotonic() # Integer time stamp for next behavior activity to begin. next_activity_time = time.monotonic_ns() + 2 * second # Flag to trigger motion. inflate_state = False phase_angle = 0.0 cycle_duration = 12 # seconds per cycle phase_rate = 2*math.pi / cycle_duration # radians/second # Integer time stamp for next servo update. next_pump_update = time.monotonic_ns() # The serial port output rate is regulated using the following timer variables. serial_timer = 0.0 serial_interval = 0.5 # ---------------------------------------------------------------- # Begin the main processing loop. while True: # Read the current integer clock. now = time.monotonic() #---- soft sensor input and display ----------------------------- # Read the integer sensor value and scale it to a value in Volts. volts = sensor.value * scaling # Normalize the soft sensor reading. Typically you'll need to adjust these values to your device. low_pressure_voltage = 0.65 high_pressure_voltage = 0.20 pressure = abs((high_pressure_voltage - volts) / (high_pressure_voltage - low_pressure_voltage)) # Check the serial input for new line of remote data if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() tokens = line.split() if len(tokens) == 1: try: remote_touch = [int(token) > 0 for token in tokens] remote_touch_timer = 4.0 except ValueError: pass #---- periodic console output ----------------------------------- # Poll the time stamp to decide whether to emit console output. if now >= sensing_timer: sensing_timer += 100000000 #0.1sec if pressure > 0.5: remote_touch_timer = now + 4000000000 # 4 sec timeout if now >= serial_timer: serial_timer += serial_interval touch = ["1" if pressure > 0.5 else "0"] print(pressure) print(" ".join(touch)) # If a slow movement has been received, sweep twice at a constant speed if inflate_state is True: pwm.duty_cycle = 2**16-1 print("Defense activated.") time.sleep(10) pwm.duty_cycle = 0 print("Defense deactivated") inflate_state = False if any(remote_touch): # Check whether there was any remote movement if remote_touch[0]: inflate_state = True print("Engage defense") remote_touch = [False] #---- periodic servo motion commands ---------------------------- # If the time has arrived to update the servo command signal: if now >= next_pump_update: next_pump_update += 20000000 # 20 msec in nanoseconds (50 Hz update)]]>
The concept of our project is broken down into two major actions and reactions. The first idea mimics the fact that our professors can be our number one fans as we draw near to end of another year. The second idea centers around how students at times need a break, a rest, and want to be able to slow down the pace that our classes enforce. These two ideas were able to be told by the pace and movements of the marionettes which are based off of Garth and Olivia.
Our idea of marionettes came to life when we thought about telling a story through fabric. We thought the best way to do this was to emote and bring to reality what control looks like. As students many of our decisions are decided by others: our advisors, professors, and ultimately our institutions. We wanted students to be in control for once.
In the video we showcase the first idea, where one sensor pad starts the movement and the other increases speed. As time goes on, professors encourage students to push through final exams and projects, so they bring more encouragement and joy to keep us afloat. This increase in speed is due to their increased motivation and their desire to see us succeed. Secondly, the marionettes move at a quickened pace, but at times students need their classes and workload to slow down. We need a break and just time to take a breath. This is very crucial since we are always on the go and this rest would alleviate unneeded stress and worry.
Process
Video
Conclusion
We wanted our movements to be very intentional; we knew the movement of the puppets would serve a great purpose and we wanted to ensure the motors could emote these feelings. With the two settings the professors either quicken in pace or slow down if the students need a break. The sensors allow for users to control the movements easily and be accessible. We decided to enclose the puppets into one stage, to truly see how the puppets take up space in one environment and how their actions look beside each other. As the Garth puppet moves quicker and quicker it travels in great distance towards the back of the box. The Garth puppet seems to be light on its feet, just flying. Whereas the Olivia puppet floats up and down and uses her light and flowy fabric to put on a show for everyone. After combining all of these elements together, we felt we were able to truly echo the presence of Garth and Olivia and explore through telematics what it means for a student to take control!
]]>The hand pump inflates the heart shaped organ. Inflating the heart tightens the vest, offering physical security to the user.
The motor pump inflates the collar. The inflated collar offers comfort and protection around the neck.
Why inflate the collar? The inflation of the collar creates comfort and protection. The comfort comes from the collar’s similarity to a travel pillow. The protection comes from the fact that the inflated collar shields a vulnerable area. Often when people are anxious, their hand goes to their neck as a subconscious way to protect themselves.
Still to do: add more movement and textiles to the collar to enhance the effect, finishing touches on the body of the vest,
Rebecca’s Vest
I am planning to use an inflatable pouch motor to slowly rise the way a garage door does to reveal the safety kit underneath. See a prototype perform that here:
It travels a considerable vertical distance.
I was alternating between the ideas of inflating one large vacuum and inflating a bunch of smaller pouches. To create each small pouch, I added an extra segment that would hold the bolt connector and be soldered off after inflating the pouch with the motor pump. However, after spending too much time troubleshooting each pouch and looking for holes and accidentally soldering off vinyl, I decided to commit to the idea of creating a one-piece vest.
Following up on the last check-in post, I created “darts” in a one piece vest to form folds around the bends of the body. With the dart:
Without the dart:
Because there is only one motor pump which is going to inflate the pouch motor shown in the video, I considered using the heart pump to inflate the one-vacuum vest. I quickly discarded that idea.
So, I have to create three separate compartments: one pouch motor, one segment to inflate with the heart pump so I can send Helen my signal, and the body of the vest (which will hold my safety kit).
Code
""" Project 2: Active Proxy Body Helen Yu (heleny1), Rebecca Kim Written by Helen Yu Last Updated: 5/4/21 Summary: When you clench your pump and inflate your vest, your partner's vest leaps into defense mode. A hobby servo helps control the defense mechanism, be it spikes, projectiles, webs, etc. Inputs: Soft Touch Sensor on A1 Outputs: Motor Pump on A2 """ # ---------------------------------------------------------------- # Import any needed standard Python modules. import time, math, sys # Import the board-specific input/output library. from adafruit_circuitplayground import cp # Import the low-level hardware libraries. import board import digitalio import analogio import pwmio # Import the Adafruit helper library. from adafruit_motor import servo # Import the runtime for checking serial port status. import supervisor # ---------------------------------------------------------------- # Initialize hardware. # Configure the digital input pin used for its pullup resistor bias voltage. bias = digitalio.DigitalInOut(board.D10) # pad A3 bias.switch_to_input(pull=digitalio.Pull.UP) # Configure the analog input pin used to measure the sensor voltage. sensor = analogio.AnalogIn(board.A1) scaling = sensor.reference_voltage / (2**16) # Create a PWMOut object (motor pump) on pad A2 to generate control signals. pwm = pwmio.PWMOut(board.A2, duty_cycle=0, frequency=2000) # ---------------------------------------------------------------- # Initialize global variables for the main loop. remote_touch = [0] # Measure the time since the last remote move message, and reset after a period without data. remote_touch_timer = False # Convenient time constant expressed in nanoseconds. second = 1000000000 # Integer time stamp for the next console output. sensing_timer = time.monotonic() # Integer time stamp for next behavior activity to begin. next_activity_time = time.monotonic_ns() + 2 * second # Flag to trigger motion. inflate_state = False phase_angle = 0.0 cycle_duration = 12 # seconds per cycle phase_rate = 2*math.pi / cycle_duration # radians/second # Integer time stamp for next servo update. next_pump_update = time.monotonic_ns() # The serial port output rate is regulated using the following timer variables. serial_timer = 0.0 serial_interval = 0.5 # ---------------------------------------------------------------- # Begin the main processing loop. while True: # Read the current integer clock. now = time.monotonic() #---- soft sensor input and display ----------------------------- # Read the integer sensor value and scale it to a value in Volts. volts = sensor.value * scaling # Normalize the soft sensor reading. Typically you'll need to adjust these values to your device. low_pressure_voltage = 0.65 high_pressure_voltage = 0.20 pressure = abs((high_pressure_voltage - volts) / (high_pressure_voltage - low_pressure_voltage)) # Check the serial input for new line of remote data if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() tokens = line.split() if len(tokens) == 1: try: remote_touch = [int(token) > 0 for token in tokens] remote_touch_timer = 4.0 except ValueError: pass #---- periodic console output ----------------------------------- # Poll the time stamp to decide whether to emit console output. if now >= sensing_timer: sensing_timer += 100000000 #0.1sec if pressure > 0.5: remote_touch_timer = now + 4000000000 # 4 sec timeout if now >= serial_timer: serial_timer += serial_interval touch = ["1" if pressure > 0.5 else "0"] print(pressure) print(" ".join(touch)) # If a slow movement has been received, sweep twice at a constant speed if inflate_state is True: pwm.duty_cycle = 2**16-1 print("Defense activated.") time.sleep(10) pwm.duty_cycle = 0 print("Defense deactivated") inflate_state = False if any(remote_touch): # Check whether there was any remote movement if remote_touch[0]: inflate_state = True print("Engage defense") remote_touch = [False] #---- periodic servo motion commands ---------------------------- # If the time has arrived to update the servo command signal: if now >= next_pump_update: next_pump_update += 20000000 # 20 msec in nanoseconds (50 Hz update)]]>
Today’s class includes one project presentation and other final interventions.
Private practice talk for the one group presenting today.
Work session for everyone else.
Near the end of class one group will present their work for critique.
Anishwar~ I’m still struggling to solve the problems from class the other day. The motor pump is still constantly pumping air; additionally, I am having trouble with getting the non-blocking version of the sound part of the code working. However, one success was getting the program to run only when there isn’t any light. While, the motor pump was still running because of the aforementioned issue the duty cycle was 0 and no sound played.
Elise~ I found new fabric that stretches well with the pneumatic. I had two versions that worked but I think a bigger inflatable makes more movement. Also tested code: It turned green when in brighter light and back to blue when in darker light. The tones changed with the light but no motor pump output.
# cpb_pump_control.py # Demonstrate control of an air pump based on a DC motor driven via a MOSFET # transistor driver circuit *or* a DRV8833 motor driver in a single-ended mode. # # 1. The driver PWM input connects to A2 # 2. The battery ground line connects to GND # Related documentation: # https://circuitpython.readthedocs.io/en/latest/shared-bindings/pwmio/index.html#module-pwmio # https://circuitpython.readthedocs.io/projects/motor/en/latest/api.html#module-adafruit_motor.servo # ---------------------------------------------------------------- # Import standard Python modules. import time, math # Import the board-specific input/output library. from adafruit_circuitplayground import cp # Import the low-level hardware libraries. import board import pwmio # ---------------------------------------------------------------- # Initialize hardware. # Create a PWMOut object on pad A2 to generate control signals. pwm = pwmio.PWMOut(board.A3, duty_cycle=0, frequency=20000) # ---------------------------------------------------------------- # Initialize global variables for the main loop. phase_angle = 0.0 cycle_duration = 12 # seconds per cycle phase_rate = 2*math.pi / cycle_duration # radians/second next_pwm_update = time.monotonic_ns() next_status_update = time.monotonic_ns() toneCheck = True nextSound = time.monotonic_ns() # ---------------------------------------------------------------- # Enter the main event loop. while True: # Read the current integer clock. now = time.monotonic_ns() soundNow = time.monotonic_ns() # If the time has arrived to update the servo command signal: if cp.light < 81: if now >= next_pwm_update: next_pwm_update += 20000000 # 20 msec in nanoseconds (50 Hz update) pwm_level = 0.5 + 0.5 * math.sin(phase_angle) # convert a unit-range (0 to 1) pwm_level to a 16-bit integer representing a fraction new_duty_cycle = min(max(int(pwm_level * 2**16), 0), 2**16-1) # If the new value is less than a reasonable minimum, clamp to zero. # The pump motor will stall if the PWM fraction is too low; this turns # it off instead. if new_duty_cycle < 48000: pwm.duty_cycle = 0 else: pwm.duty_cycle = new_duty_cycle phase_angle = (phase_angle + phase_rate * 0.020) % (2 * math.pi) # If either button is pressed, override the generated servo signal and run the motor. if cp.button_a or cp.button_b: pwm.duty_cycle = 2**16-1 # If the time has arrived to display the status: if now >= next_status_update: next_status_update += 500000000 # 0.5 sec in nanoseconds (2 Hz update) print(pwm.duty_cycle) if cp.light < 81 and cp.light >= 36: cp.play_tone(329,.5) cp.play_tone(493,.5) cp.pixels.fill((0,255,0)) elif cp.light < 36: cp.play_tone(293,.5) cp.play_tone(466,.5) cp.pixels.fill((0,0,255)) else: pwm.duty_cycle = 0 print("ok")
Below is my attempt to try making the sounds play using a non-blocking code; however, it doesn’t seem to be working and I need to continue messing with it.
Elise~ tested and needed A2 to change to A3 like previous code. Motor and light combination has some issues moving smoothly.
# cpb_pump_control.py # Demonstrate control of an air pump based on a DC motor driven via a MOSFET # transistor driver circuit *or* a DRV8833 motor driver in a single-ended mode. # # 1. The driver PWM input connects to A2 # 2. The battery ground line connects to GND # Related documentation: # https://circuitpython.readthedocs.io/en/latest/shared-bindings/pwmio/index.html#module-pwmio # https://circuitpython.readthedocs.io/projects/motor/en/latest/api.html#module-adafruit_motor.servo # ---------------------------------------------------------------- # Import standard Python modules. import time, math # Import the board-specific input/output library. from adafruit_circuitplayground import cp # Import the low-level hardware libraries. import board import pwmio # ---------------------------------------------------------------- # Initialize hardware. # Create a PWMOut object on pad A2 to generate control signals. pwm = pwmio.PWMOut(board.A2, duty_cycle=0, frequency=20000) # ---------------------------------------------------------------- # Initialize global variables for the main loop. phase_angle = 0.0 cycle_duration = 12 # seconds per cycle phase_rate = 2*math.pi / cycle_duration # radians/second next_pwm_update = time.monotonic_ns() next_status_update = time.monotonic_ns() toneCheck = True nextSound = time.monotonic_ns() # ---------------------------------------------------------------- # Enter the main event loop. while True: # Read the current integer clock. now = time.monotonic_ns() soundNow = time.monotonic_ns() # If the time has arrived to update the servo command signal: if now >= next_pwm_update: next_pwm_update += 20000000 # 20 msec in nanoseconds (50 Hz update) pwm_level = 0.5 + 0.5 * math.sin(phase_angle) # convert a unit-range (0 to 1) pwm_level to a 16-bit integer representing a fraction new_duty_cycle = min(max(int(pwm_level * 2**16), 0), 2**16-1) # If the new value is less than a reasonable minimum, clamp to zero. # The pump motor will stall if the PWM fraction is too low; this turns # it off instead. if new_duty_cycle < 48000: pwm.duty_cycle = 0 else: pwm.duty_cycle = new_duty_cycle phase_angle = (phase_angle + phase_rate * 0.020) % (2 * math.pi) # If either button is pressed, override the generated servo signal and run the motor. if cp.button_a or cp.button_b: pwm.duty_cycle = 2**16-1 # If the time has arrived to display the status: if now >= next_status_update: next_status_update += 500000000 # 0.5 sec in nanoseconds (2 Hz update) print(pwm.duty_cycle) if cp.light < 81 and cp.light >= 36: print(soundNow) print(nextSound) if soundNow >= nextSound: print("something") cp.start_tone(329) nextSound = soundNow + 500000000 cp.stop_tone() else: print("cool") cp.start_tone(493) cp.stop_tone() elif cp.light < 36: cp.play_tone(293,.5) cp.play_tone(466,.5) else: cp.stop_tone()]]>
import time import supervisor import sys # Import the low-level hardware libraries. import board import digitalio import analogio import pwmio from adafruit_circuitplayground import cp from adafruit_motor import servo # ---------------------------------------------------------------- # Initialize hardware. # Create a PWMOut object on pad SDA A5 to generate control signals. pwm = pwmio.PWMOut(board.A5, duty_cycle=0, frequency=50) # Create a Servo object which controls a hobby servo using the PWMOut. actuator = servo.Servo(pwm, min_pulse=1000, max_pulse=2000) # Create a PWMOut object on pad SCL A4 to generate control signals. pwm2 = pwmio.PWMOut(board.A4, duty_cycle=0, frequency=50) # Create a Servo object which controls a hobby servo using the PWMOut. actuator2 = servo.Servo(pwm2, min_pulse=1000, max_pulse=2000) # Configure the digital input pin used for its pullup resistor bias voltage. bias = digitalio.DigitalInOut(board.D10) bias.switch_to_input(pull=digitalio.Pull.UP) # Configure the analog input pin used to measure the sensor voltage. sensor = analogio.AnalogIn(board.A2) scaling = sensor.reference_voltage / (2**16) last_touch = 0 touched_arm_one = 0 touched_arm_two = 0 touched_leg_one = 0 touched_leg_two = 0 def flush_check(): if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() # ---------------------------------------------------------------- # Begin the main processing loop. while True: # Read the integer sensor value and scale it to a value in Volts. volts = sensor.value * scaling touched_arm_one = 0 pressure_one = (3.2 - volts) / (3.2 - 0.4) print(pressure_one) #function to send data pressure_two = (1.4 - volts) / (1.4 - 0.1) if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() tokens = line.split() if len(tokens) == 1 and float(tokens[0]) > .90: touched_arm_one = 1 if touched_arm_one: for angle in range(0, 180, 5): actuator.angle = angle time.sleep(0.02) flush_check() for angle in range(180, 0, -5): actuator.angle = angle time.sleep(0.02) flush_check() time.sleep(.5)]]>
The two of us were interested in exploring a rivalry between two devices that respond to very deliberate stimulus and communicate that stimulus between each other. We were drawn to the idea of creating small expressive creatures that beg for the users attention and compete with each other almost as siblings do. We were also interested in creating a sort of ‘counter’ that is communicated back and forth through the MQTT connection in order to keep track of which Desk Pet is in the lead and which is losing.
We see the narrative as two connected desk pets that both seek to gain more attention from their users than the other. They communicate to the user and to each other through idle movement, boasting when they receive more attention, and begging when they are receiving less than their other pair.
Our initial ideas for how to express a rivalry between two Desk Pets was through the stretching and folding of fabric. In order to determine the best type of fabric to use and the best type of connection to the servos, we did a few material studies.
The two of us quickly found that muslin and wooden sticks were too stiff to generate our desired effects. From there, we moved on to fabrics that stretched more.
Along with the fabric tests, we also considered different mounting options for the servos. We began rather ambitious, expecting the use of 4 servos per person. By the end, we were able to cut the servos down to 2 per person, but maintain 4 points of contact by doubling up on each servo.
Once we had the method of movement and the best type of fabric and connection figured out, we were driven to finding a better form for out Desk Pet. The flat fabric was ideal for stretching, but it lacked depth and character. This inspired the new form of the Desk Pet that also included a more expressive face.
This new form inspired our idea for where to place the touch sensor. Making the head itself the sensor proved to be too difficult, but it shone a light on the option of making the touch sensor something detached that still rests atop the head- a hat! We stuck a touch sensor between the fabric attached to the hat to pit it between the form and the fabric and connected it accordingly to allow for the Desk Pet to respond to touch stimulus.
We created three different states for our Desk Friends.
Jealous begging and happy movements:
Lonely movements:
Our final Desk Pet looks like this:
# Kinetic Fabrics S21 # Kanvi and Yael # ----------------------------------------- # Import the standard Python time functions. import time # Import the low-level hardware libraries. import board import digitalio import analogio import pwmio from adafruit_circuitplayground import cp from adafruit_motor import servo import supervisor import sys analogin = analogio.AnalogIn(board.A2) # Create a PWMOut object on pad SDA A5 to generate control signals. pwm1 = pwmio.PWMOut(board.A5, duty_cycle=0, frequency=50) # Create a PWMOut object on pad A3 to generate control signals. pwm2 = pwmio.PWMOut(board.A3, duty_cycle=0, frequency=50) # Create a Servo object which controls a hobby servo using the PWMOut. servo1 = servo.Servo(pwm1, min_pulse=1000, max_pulse=2000) # Create a Servo object which controls a hobby servo using the PWMOut. servo2 = servo.Servo(pwm2, min_pulse=1000, max_pulse=2000) # adjust these for indivudal projects HAPPY_START = 0 HAPPY_STOP = 30 HAPPY_SERVO2_OFFSET = 60 BEG_START = 90 BEG_STOP = 10 ######################################### def getVoltage(pin): # helper return (pin.value * 3.3) / 1023 def happy(level): for i in range(10): for angle in range(HAPPY_START, HAPPY_STOP, 1): servo1.angle = angle servo2.angle = angle + HAPPY_SERVO2_OFFSET time.sleep(0.1 / level) for angle in range(HAPPY_STOP, HAPPY_START, -1): servo1.angle = angle servo2.angle = angle + HAPPY_SERVO2_OFFSET time.sleep(0.1 / level) def beg(): for i in range(10): for angle in range(HAPPY_START, HAPPY_STOP, 1): servo1.angle = angle time.sleep(0.1) for angle in range(HAPPY_START, HAPPY_STOP, 1): servo2.angle = angle + HAPPY_SERVO2_OFFSET time.sleep(0.1) for angle in range(HAPPY_STOP, HAPPY_START, -1): servo1.angle = angle time.sleep(0.1) for angle in range(HAPPY_STOP, HAPPY_START, -1): servo2.angle = angle + HAPPY_SERVO2_OFFSET time.sleep(0.1) def neglect(): for i in range(2): for angle in range(BEG_START, BEG_STOP, -1): servo2.angle = angle time.sleep(0.02) for angle in range(BEG_STOP, BEG_START, 1): servo2.angle = angle time.sleep(0.02) for angle in range(BEG_STOP, BEG_START, 1): servo1.angle = angle time.sleep(0.02) for angle in range(BEG_START, BEG_STOP, -1): servo1.angle = angle time.sleep(0.02) petCount = 0 lonely = 0 while True: if supervisor.runtime.serial_bytes_available: line = sys.stdin.readline() print("hi") if line == "beg lots": reading = getVoltage(analogin) # print("Analog Voltage: %f" % reading) while reading < 10: beg() happy(16) reading = getVoltage(analogin) #print("Analog Voltage: %f" % reading) if reading > 10: petCount += 1 else: lonely += 1 if petCount == 16: print("beg lots") happy(petCount) petCount = 0 lonely = 0 if lonely > 50: neglect() lonely = 0 time.sleep(0.2)
This project was so much fun! We had a lot of fun building these Desk Pets and their accompanying code. The material and servo studies at the beginning were extremely helpful in informing our final outcome and we expect these studies to continue to inform our design decisions in future projects to come. The combination of several servos and various levels of input were also challenging to implement at first but very worthwhile by the end of the project.
There is so much more that the two of us would love to develop had we more time to continue working on these Desk Pets. Our initial concepts included multiple stimulus inputs including light and sound along with touch. We both feel that had we more time, the project would be much more compelling with those additional inputs to create different outputs that aren’t in the current project. In our initial concept, we also discussed having a visual ‘counter’ that displayed how ahead or behind the users Desk Pet was with attention received. We chose to leave it out in this iteration due to not having a good way of connecting the circuit playground to the constructed Desk Pet. We felt that by having the counter elsewhere on the table, flashing bright lights at the user, it would be too distracting and take away from the interesting folding and stretching of the fabric.
]]>Anishwar: I experimented with using the DC Motor Pump to inflate a pneumatic that would correspond differently to different eyes. I think this experiment was quite successful at creating a pneumatic that could inflate two different areas of the mask.
]]>