How to stream your Raspberry Pi camera (using PiCamera2) as mjpg

Views: 25

A Raspberry Pi with an attached PiCamera can work as a simple surveillance system. A very simple way to stream the images is using MJPEG.

To quickly set up streaming MJPEG, I created a Python script that encodes the images in separate threads. Therefore the whole CPU of the Pi can be utilized. The maximum framerate is equal to what the camera can deliver (47 frames). On a Pi 4 this makes the CPU quite hot, so the script allows to throttle the throughput. 25 FPS is easily possible without any cooling.

Prerequisites

A Raspberry Pi with an attached camera module. I tested this on a Pi 2, 3 and 4, it probably works on a Pi 1 just as well, albeit with a severely reduced framerate.

Install the latest version of the OS:

sudo apt update
sudo apt upgrade

Install necessary python modules

sudo apt install python3-flask python3-libcamera python3-picamera2 python3-opencv

And then run the script

python pystream.py

The stream will be available at port 9000 of your Raspberry Pi: http://your-pi-IP:9000/mjpg

If you run this in a terminal, the process will of course stop once the window is closed, unless you run it using screen. If you haven’t installed it yet:

sudo apt install screen

Start the screen by just typing

screen

A short explanation will show, you can end that with <RETURN>. Anything you start in this will continue to run, even if you close the window.

To get back to a running screen process, just type

screen -r

The script (download here):

#!/usr/bin/env python3

from flask import Flask, Response
import cv2
from picamera2 import Picamera2
import libcamera
import time
import threading

# Creates an mjpg stream from a PiCamera. It's a quick and dirty example how to use your Pi as a surveillance camera
# Uses 2 separate threads to encode the captured image to maximize throughput by using all 4 cores
# The way this "double-buffering" is implemented causes it to only work correctly with one client. 


# Set your desired image size here
# The timing for approximating the frames per second depends largely on this setting
# Larger images means more time needed for processing
CAM_X = 1280
CAM_Y = 720


# Change this, depending on the orientation of your camera module
CAM_HFLIP = False
CAM_VFLIP = False

# Change this to control the usage and therefore temperature of your Pi. On my Pi 4 a setting of 25 FPS
# results in CPU usage of roughly 40% and no temperature throttling (no additional cooling here)
# Set to 0 to impose no restrictions (on my Pi 4 this results in ~47 FPS (maximum of my PiCamera model 2), on my Pi 2 ~17 FPS)
MAX_FPS = 25

# Flask is our "webserver"
# The URL to the mjpg stream is http://my.server:WEB_PORT/mjpg
WEB_PORT = 9000
app = Flask(__name__)

# Keeps all data for the various threads in one place
class MgtData(object):
    stop_tasks = False            # If set, all threads should stop their processing
    frame1_has_new_data = False   # Is being set when frame1 receives a new buffer to encode
    lock1 = False                 # Is being set when frame1 receives a new buffer to encode, and cleared when encoding is done
    frame2_has_new_data = False   # Same for frame 2
    lock2 = False

    img_buffer1 = None            # Receives the image as a byte array for frame 1
    img_buffer2 = None            # ... for frame 2
    encoded_frame1 = None         # Stores the JPG-encoded image for frame 1
    encoded_frame2 = None         # ... for frame 2

    # If there is new data available on frame 1, return True
    def frame1_new_data():
        return (MgtData.frame1_has_new_data and not MgtData.lock1)

    # If there is new data available on frame 2, return True
    def frame2_new_data():
        return (MgtData.frame2_has_new_data and not MgtData.lock2)


# Deliver the individual frames to the client
@app.route('/mjpg')
def video_feed():
    generated_data  = gen()
    if (generated_data):
        response = Response(gen(), mimetype='multipart/x-mixed-replace; boundary=frame') 
        response.headers.add("Access-Control-Allow-Origin", "*")
        return response
    return None

# Generate the individual frame data
def gen():
    while not MgtData.stop_tasks:  
        while (not (MgtData.frame1_new_data() or MgtData.frame2_new_data())):
            time.sleep (0.01) # Wait until we have data from one of the encode-threads

        frame = get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

# If one of the frames has data already processed, deliver the respective encoded image
def get_frame():
    encoded_frame = None
    if (MgtData.frame1_new_data() or MgtData.frame2_new_data()):
        if (MgtData.frame1_new_data ()):
            encoded_frame = MgtData.encoded_frame1
            MgtData.frame1_has_new_data = False
        elif (MgtData.frame2_new_data ()):
            encoded_frame = MgtData.encoded_frame2
            MgtData.frame2_has_new_data = False
    else:
        print ("Duplicate frame")

    return encoded_frame




# Start the server
def start_webserver():
    try:
        app.run(host='0.0.0.0', port=WEB_PORT, threaded=True, debug=False)
    except Exception as e:
        print(e)

# Definition for the encoding thread for frame 1
def encode1():
    newEncFrame = cv2.imencode('.jpg', MgtData.img_buffer1)[1].tobytes()
    MgtData.encoded_frame1 = newEncFrame
    MgtData.frame1_has_new_data = True
    MgtData.lock1 = False

# Definition for the encoding thread for frame 2
def encode2():
    MgtData.lock2 = True
    MgtData.frame2_has_new_data = True
    newEncFrame = cv2.imencode('.jpg', MgtData.img_buffer2)[1].tobytes()
    MgtData.encoded_frame2 = newEncFrame
    MgtData.lock2 = False



def run_camera():
    # init picamera
    picam2 = Picamera2()

    preview_config = picam2.preview_configuration
    preview_config.size = (CAM_X, CAM_Y)
    preview_config.format = 'RGB888'
    preview_config.transform = libcamera.Transform(hflip=CAM_HFLIP, vflip=CAM_VFLIP)
    preview_config.colour_space = libcamera.ColorSpace.Sycc()
    preview_config.buffer_count = 4 # Looks like 3 is the minimum on my system to get the full 47 FPS my camera is capable of
    preview_config.queue = True
    preview_config.controls = {'FrameRate': MAX_FPS and MAX_FPS or 100}

    try:
        picam2.start()

    except Exception as e:
        print(e)
        print("Is the camera connected correctly?\nYou can use \"libcamea-hello\" or \"rpicam-hello\" to test the camera.")
        exit(1)
    
    fps = 0
    start_time = 0
    framecount = 0
    try:
        start_time = time.time()
        while (not MgtData.stop_tasks):
            if (not (MgtData.frame1_new_data() and MgtData.frame2_new_data())):

                # get image data from camera
                my_img = picam2.capture_array()

                # calculate fps
                framecount += 1
                elapsed_time = float(time.time() - start_time)
                if (elapsed_time > 1):
                    fps = round(framecount/elapsed_time, 1)
                    framecount = 0
                    start_time = time.time()
                    print ("FPS: ", fps)

                # if one of the two frames is available to store new data, copy the captured image to the
                # respective buffer and start the encoding thread
                # At max we have 4 threads: our main thread, flask, encode1 and encode2
                if (not MgtData.frame1_new_data()):
                    MgtData.img_buffer1 = my_img
                    MgtData.frame1_has_new_data = True
                    MgtData.lock1 = True
                    encode_thread1 = threading.Thread(target=encode1, name="encode1")
                    encode_thread1.start()
                elif (not MgtData.frame2_new_data()):
                    MgtData.img_buffer2 = my_img
                    MgtData.frame2_has_new_data = True
                    MgtData.lock2 = True
                    encode_thread2 = threading.Thread(target=encode2, name="encode1")
                    encode_thread2.start()
            time.sleep (0.0005) # No need to constantly poll, cut the CPU some slack
            
    except KeyboardInterrupt as e:
        print(e)
        MgtData.stop_tasks
    finally:
        picam2.close()
        cv2.destroyAllWindows()



def streamon():
    camera_thread = threading.Thread(target= run_camera, name="camera_streamon")
    camera_thread.daemon = False
    camera_thread.start()

    if camera_thread != None and camera_thread.is_alive():
        print('Starting web streaming ...')
        flask_thread = threading.Thread(name='flask_thread',target=start_webserver)
        flask_thread.daemon = True
        flask_thread.start()
    else:
        print('Error starting the stream')

    while not MgtData.stop_tasks:
        time.sleep (25) # Just waiting to end this thread



if __name__ == "__main__":
    try:
        streamon()
    except KeyboardInterrupt:
        pass
    except Exception as e:
        print(e)
    finally:
        print ("Closing...")
        MgtData.stop_tasks = True

Home Automation für mich – erste Schritte

Views: 284

ZigBee ist also ein Funkprotokoll, mit dem IoT-Geräte sich mitteilen können. Es ist nicht WiFi, also muss ich den Geräten auch nicht einen potentiellen Zugang zum Internet bieten. Es ist ein etablierter Standard, also erwarte ich, dass das auch funktioniert.

ZigBee benötigt eine Empfangsstation, also habe ich mir einen Sonoff ZigBee 3.0 USB Stick sowie einen ESSENTIALS Heizkörper-Thermostat Zigbee gekauft. Ich empfehle Geräte grundsätzlich nur und dann auch explizit, wenn ich der Meinung bin, dass sie gut sind. Diese beiden Geräte erwähne ich nur, weil ich jetzt mit denen angefangen habe.

Weil ich dachte, bzw. eher nur hoffte, dass wir vielleicht mit Heinautomatisierung bereits an wirklich vernünftigen Standards angekommen sind, bin ich erstmal davon ausgegangen, dass ich einfach Home Assistant installieren könne und alles funktioniert wie Magie.

Home Assistant kann unter anderem mittels HAOS (Home Asssistant Operating System) direkt auf einem Raspberry Pi ausgeführt werden, im Idealfall braucht es nur etwas Zeit und dann ein Pairing mit allen Geräten. Benutzt man HAOS, dann richtet sich weitgehend alles selber ein, der USB-Stick wurde erkannt, aber nach dem ersten Boot mit der neu geschriebenen SD-Karte dauert die Einrichtung noch fast eine halbe Stunde. Allerdings ist alles noch automatisch und man muss nur warten.

Unter http://homassistant:8123/ steht dann eine Weboberfläche zur Verfügung. Mit ein paar Klicks konnte ich dann nach ZigBee-Geräten suchen lassen. Diese müssen sich im Pairing-Modus befinden. Wie das aber bei dem Thermostat geht, dazu lässt sich die Betriebsanleitung nicht aus. In der steht

Die Steuerung und Verbindung mit dem Smartphone sowie der
essentials Smart Home App erfordert zwingend den Einsatz der
essentials Smart Home Zentrale.

Und wenn man die Smart Home Zentrale gekauft hat, dann soll die App die Information raus geben, wie der Thermostat in den Pairing-Modus gebracht wird. Andernfalls darf ich nicht wissen, wie das geht.

ESSENTIALS Heizkörper-Thermostat Zigbee Pairing: oberen Knopf für 4 Sekunden halten

Wenn der Thermostat in den Pairingmodus geht, kommt kurz auf dem LCD das Wort “Pair”. Home Assistant findet dann auch ein Gerät, allerdings weiß HA nicht, was es damit anfangen soll.

Zu dem Thermostat kamen nur 2 Datenpunkte automatisch ins System, RSSI (received signal strength indication) und LQI (link quality indicator), mehr nicht.

So direkt weiß ich nicht weiter. Ich kenne mich auch nicht genug mit ZigBee bzw. home automation aus, um zu beurteilen, wo das eigentliche Problem liegt.

  • Ist der Standard einfach nicht ausreichend, um – z.B. wie bei Bluetooth – alle wichtigen Gerätetypen per Class compliance zu erkennen und anzusprechen?
  • Ist Home Assistant einfach nicht gut genug?
  • Möchten alle Hersteller von Geräten vielleicht möglichst nicht kooperieren?

Ich vermute, dass es letzteres ist. Jeder Hersteller von Home automation Geräten will immer noch zusätzlich seine höchst eigene Zentrale mit verkaufen und den Anwender in sein Ökosystem zwingen. Dann muss das Opfer der Kunde alle zusätzlichen Geräte auch von diesem Ökosystem kaufen.

Nicht nur, dass ich das für sehr kundenunfreundlich halte, ich bin auch der Meinung, dass dadurch viel Elektromüll produziert wird. Weiterhin bin ich der Meinung, dass sich solche Hersteller gerade über längere Sicht selber damit ins Aus manövrieren. Gerade, wenn ich eine größere Installation plane und z.B. alles in meinem Haus automatisierbar machen möchte, muss ich mir Gedanken über Interoperabilität oder Zukunftssicherheit machen. Wird <kleiner Hersteller> noch in 3 Jahren existieren und weitere Geräte / Ersatzteile liefern können? Wird <kleiner Hersteller> Firmwareupdates liefern, wenn Sicherheitslücken bekannt werden? Wird der Cloudservice von <kleiner Hersteller> in 2 Jahren noch am Netz sein? Stehen die Server für die Dienste von <kleiner Hersteller> alle in China?

Bei diesen Fragen muss ich eigentlich zu dem Schluss kommen, dass <kleiner Hersteller> nicht in die engere Wahl kommen kann, und <großer Monopolist> auf dem Papier die sicherere Möglichkeit ist. Im Linux Magazin von 2017 ist das Dilemma schon beschrieben:

Und es gibt weitere gute Gründe, die gegen die fertige Hue Bridge von Philips sprechen: Wer etwa die schlauen Lampen von Ikea (Trådfri) besitzt, kann diese nicht mit dem Hue-Gateway verbinden. Denn die Ikea-Lampen sprechen ZHA, während Hue zwingend ZLL als Zigbee-Profil will. Im schlimmsten Falle muss man also zwei separate Steuergeräte betreiben, die voneinander nichts wissen: Komfortabel geht anders.

Natürlich könnte man verschiedenste Zentralen kaufen und diese dann über Home Assistant (in vielen Fällen zumindest) koordinieren, aber warum sollte ich mehr als eine Zentrale überhaupt haben? Zusätzlich ist es auch nicht so, dass <großer Hersteller> notgedrungen für Qualität steht, besonders schön beschrieben in einem Video von Linus Tech Tips.

Das vorläufige Fazit ist auf jeden Fall, dass es auch heute nicht möglich ist, einfach Heimautomatisierungs-Bestandteile zu kaufen und sie “einfach so” einzusetzen. Mindestens muss man sich lange mit Kompatibilitäten auseinander setzen.

Ich experimentiere jetzt erstmal mit Zigbee2MQTT rum, da ich da zumindest schonmal die Meldungen vom Thermostat roh sehen kann.

Home automation für mich

Views: 207

Meine Heizung soll besser für mich funktionieren. D.h. dass nur geheizt werden soll wo und wann ich das möchte. In meiner Wohnung gibt es 5 Räume und einen Thermostat. Dieser Thermostat ist in der Küche montiert – die Küche gehört aber zu den Räumen, die ich am wenigsten heizen möchte.

Damit ein Thermostat an- und ausschalten kann, benötigt er neben einer Zieltemperatur, die über der aktuellen Raumtemperatur liegt (zum Anschalten) einen heizenden Heizkörper, damit die Raumtemperatur zur Zieltemperatur steigen kann (zum Ausschalten). Entweder heize ich die Küche gar nicht – dann kann der Thermostat aber auch nicht aus schalten, oder ich heize sie mit und rate, welche Temperatur in der Küche sein muss, wenn ich in meinen Wohnräumen vernünftige Bedingungen haben möchte. Allerdings, abhängig gerade auch von der Außentemperatur, ist das alles nur ein Raten.

Das Ziel für mich ist, 2 Räume sollen kontrolliert geheizt werden, 1 Raum bekommt etwas vom Heizwasser ab, und 2 Räume werden nicht geheizt. Solange einer der kontrollierten Räume noch geheizt werden muss, muss die Gastherme an sein (was sonst die Aufgabe des Thermostats in der Küche wäre). In den zu heizenden Räumen müssen Heizkörperthermostate jeweils dort die Wärme kontrollieren.

An-/Ausschalten der Therme ist eigentlich ganz einfach, da ist eine Signalleitung, die 0V – 24V hat. Mit einer simplen Transistorschaltung kann ein Raspberry-Pi das schalten. Ich habe mir da auch eine simple Weboberfläche für geschrieben, mit der ich die Therme (eigentlich die Pumpe der Therme) an und aus schalten kann, sowie simple Vorprogrammierung. Da die Schaltung parallel zu der bestehenden Funktion des Thermostats existiert, kann der originale Thermostat noch als Frostschutz funktionieren. Allerdings habe ich damit noch keine detaillierte Kontrolle darüber, wie warm die einzelnen Räume werden.

Also benötige ich ein Temperatur-Feedback sowie Thermostate an den einzelnen Heizkörpern.

Für mich klingt das so, als müsste das durch die eine oder andere Heimautomatisierung machbar sein.

Und jetzt versuche ich, zu verstehen, wie ich das hinkriegen kann. Insbesondere, da sämtliche Geräte nicht nach Hause telefonieren sollen. Alles soll über einen lokalen Server gehen (wie das von außen steuerbar ist, ist erstmal ein nachrangiges Problem). Ich möchte nicht, dass meine Steuerung davon abhängig ist, dass ein Hersteller einen Cloudservice auch in einem Jahr noch anbietet. Ich möchte auch nicht, dass der Cloudservice eines Herstellers die Information erhält, wie mein Lebensrhythmus ist.

Ein erster Versuch von mir, die Zusammenhänge und Anforderungen an die einzelnen Komponenten zu verstehen, brachte mir nur noch mehr Konfusion. Entweder bin ich nicht in der Lage, richtig zu suchen, oder es gibt sehr wenig Material für einen ersten Einstieg. Wäre ich nicht so paranoid, würde ich jetzt vielleicht doch irgendeinen scheinbaren Standard mit Cloud Service nehmen und dann unnötige Daten über mich auf Amazon Web Services speichern lassen, es scheint furchtbar kompliziert.

Glücklicherweise haben mir nette Benutzer auf Reddit geholfen, meine erste Verständnisblockade zu beheben.

Theoretisch sollte es also so funktionieren, dass ich an meinen lokale Server einen Zigbee-USB-Stick stecke, der dann mit einem Zigbee-Thermostat kommuniziert. Soweit so gut, insbesondere, da das Gerät dann nicht unkontrolliert im WLAN hantiert, sondern es ein anderer Kommunikationsweg ist.

Ich bin gespannt, was der Thermostat wirklich kann, insbesondere in Zusammenarbeit mit einem lokalen Server. In meiner Vorstellung kann ich nicht nur dem Thermostat mitteilen, dass er anmachen soll, sondern auch, dass ich Feedback bekomme, ob der Thermostat an ist, welche Temperatur er misst etc. Thermometer haben die alle, damit sie auch ihre eigene Funktion steuern können.

Wahrscheinlich wird es dann aber eher so sein, dass ich nur dem Thermostat eine Zieltemperatur geben kann, und es kommen keine auswertbaren Daten zurück. Aber ich weiß es im Moment noch nicht, die technischen Daten schweigen sich über so etwas natürlich aus.

Crumar Organizer T1 / T2 Netzteil

Views: 353

Beide Geräte, Crumar Organizer T1 und T2, benutzen das gleiche Netzteil. Ein Transformator mit Mittenabgriff produziert Wechselspannung, die in einen Brückengleichrichter geht und dann +/- 22-24 Volt gegen Masse bzw. den Mittenabgriff darstellt. Über Spannungsregulierer (78M15 für positiv, 79M15 für negativ) werden dann die +/- 15V erzeugt, mit denen ein T1 / T2 arbeitet.

Fehlersuche

Alle T1 / T2, die ich bisher in den Händen hatte, hatten mindestens einen Fehler im Netzteil. Dank des übersichtlichen Aufbaus ist eine Fehlersuche recht einfach. In den meisten Fällen brennen die beiden 1A-Sicherungen (6) direkt beim Einschalten durch, daher sind die zuerst zu prüfen. Wenn die Sicherungen durchgebrannt sind, kann man natürlich sein Glück versuchen, die Sicherungen austauschen und hoffen, dass die nur zufällig durchgebrannt sind – ja, so etwas kommt vor, aber das habe ich bei diesen Netzteilen noch nicht gesehen.

Da die Spannungsregler (13) per Design auf 500 mA begrenzt sind (und +300mA / -200mA im Schaltbild gefordert werden), müssten die Sicherungen auch bei kurzfristig erhöhter Last funktionieren. Die Spannungsregler sind in Realität irgendwelche, je nachdem, was Crumar zu dem Zeitpunkt ergattern konnte. Hauptsache +/- 15V.

Wenn die Sicherungen aufgeben kann ich mir hier sicher sein, dass ein echter Kurzschluss vorliegt. Bei all meinen Netzteilen waren die 1µF-Kondensatoren (9/11/14) Tantal-versionen, und bei allen haben diese Kondensatoren aufgegeben was sich meistens als Kurzschluss gezeigt hat. Bei den größeren Elkos (laut Schaltbild beide 2000 µF, in Realität war aber immer der Kondensator in der negativen Spannungsversorgung lediglich ein 1000 µF) war es bisher 50/50 ob die defekt waren. In jedem Fall lohnt es sich, die Tantals mindestens an einem Beinchen auszulöten. Ich habe es bei 2 Netzteilen ausprobiert, dass ich die größeren Kondensatoren austausche, aber die Tantals drin lasse. Beides Mal explodierten die Tantals in der selben Reihenfolge – positiv vor Spannungsregler, negativ vor, positiv nach, negativ nach Spannungsregler.
Ich würde daher im Netzteil einfach sofort alle Kondensatoren tauschen.

Mir ist es noch nicht passiert, dass einer der Spannungsregler defekt wäre. Wären die direkt auf der Platine, würde ich die aber auch direkt austauschen, die Teile kosten nichts. Allerdings sind die Regler mit einer Steckplatine und dünnen Drähtchen verbunden. Ich möchte immer soweit möglich alles so original wie möglich erhalten, daher bleiben die Regler, wo sie sind. Würde ich da etwas anpassen wollen, dann würde ich mir direkt 2 15Volt Netzteile kaufen und die als vollen Austausch vom Originalnetzteil nehmen. Schon alleine, weil moderne Netzteile viel effizienter arbeiten. Das musste ich bei einem T1 machen, da dort der Transformator selber einen Kurzschluss hatte, und ich ehrlich gesagt keine Lust hatte, dann noch einen Ersatztrafo zu finden.

Wenn die Glimmlampe im Schalter nicht leuchtet, ist entweder das Kaltgerätekabel zum Netzteil kaputt, die 250 mA-Sicherung durchgebrannt, der Schalter defekt oder einfach die Glimmlampe hinüber – defekte Glimmlampe hatte ich schon, aber keins der anderen Probleme.

Mechanisches

Um an die Lötseite der Hauptplatine zu kommen, müssen 4 Schrauben gelöst werden. Die beiden näher am Transformator haben Abstandshalter während die beiden anderen Winkel zum Fixieren der Platine haben. Für mich ist die Schraube rechts oben im Bild nicht erreichbar, spätestens beim Einbau habe ich mit meinen Fingern keine Chance mehr.
Aber der Sicherungshalter und der Kühlkörper mit den Spannungsreglern kann abgeschraubt werden. Etwas Vorsicht ist hier geboten, ich reiße regelmäßig die dünnen Drähtchen ab und muss sie dann wieder anlöten.

Wenn alles gut gegangen ist, alle Kondensatoren ausgetauscht und neue Sicherungen eingesetzt sind, sollte das Netzteil am großen Steckverbinder (15) eine direkte Verbindung zu Erde haben, der kleinere daneben (16) eine Spannung von etwa -15V und der dritte (17) etwa +15V haben.

Da das Netzteil nicht immer (alleine) Schuld an Problemen ist, hilft eine kurze Messung bei dem ausgesteckten Stromanschluss (also hinter dem Netzteil). Wenn zwischen Masse und den +/- 15 Volt-Anschlüssen ein Widerstand im Bereich von 20kOhm besteht, kann man die Stromversorgung verbinden und alles könnte wieder funktionieren.

Bei einem T2 habe ich derzeit zwischen +15V und Masse gerade mal 63 Ohm, und das sinkt noch, wenn wirklich Spannung anliegt. Damit habe ich an diesem Gerät dann doch mal den positiven Spannungsregler erledigt.

Korg i3 – power rail repair

Views: 904

I bought a Korg i3 – broken – from ebay, the seller claimed it just stopped working. If that is true, usually the power rail is to blame. And in most cases it’s a dead capacitor. No big deal – usually.

The first board to come out is KLM-1631:

And a check on the components showed me that something let the magic smoke out – LC1. And it was right at the connector to the power supply:

The power supply looked good, so I assumed this was the culprit.

According to the Service manual, LC1 is a DST310, a component I hadn’t seen before, but I had guessed right, a blown capacitor.

I don’t have these and couldn’t find a source – maybe these aren’t produced anymore? But it’s just a power filter, I could probably get away with just connecting CN8A-5 directly to the “A” point. But having capacitive storage in a power rail is always a good thing. And since I’m already in there, I can do at least a decent job.

So I desoldered this LC1, cleaned the board and had to find the traces were eaten away. I additionally removed C92 as these capacitors were just in parallel. Also, maybe C92 was damaged by some surge – it was in the way and costs next to nothing. Only to find out that the trace going to C92 had left us as well.

So, if in doubt – scream and shout. Uhm… these are buffer capacitors, so they just sit between plus and minus, both traces were large enough to just scratch the solder screen off and solder in capacitors. Again, I could probably have gotten away with just one, but the original schematic had two, so I used two:

Yep, it’s ugly. Yep, I probably should have used a ceramic for the smaller one, but seriously, it is the 5V rail, quite likely this is the supply for the logic and won’t have any effect on the sound.

Starting up, it works:

So, I was right to assume a blasted capacitor in the power rail was to blame.

But, as Dr. House kept reminding us: people lie.

Next up: Repairing Power-On-Mute on an i3.

Salesforce Mailchimp integration update 1.93.2

Views: 1516

Unfortunately Mailchimp created a little hickup in the update 1.93.2 (2020-08-11) to their Salesforce integration. When you open the setup “MC Setup” you might be greeted with an error message:

“Read” permission to field “MC4SF__Prompt_For_Full_Sync__c on object “MC4SF__MC_List__c” is not allowed for the current user.

I had seen this on my Developer Edition org at first and contacted the Mailchimp support. Sadly it doesn’t seem to ring enough alarm bells, the final answer was “Since the error has a quick fix and it’s not causing an issue in all accounts, we will be keeping an eye out for further instances to see if there are any similarities.” It seems a bit strange to me, as this is breaking existing installations when the update is applied.

However, there is a solution. In the field level security settings of the field “Prompt For Full Sync” on “MC Audience” you have to give read access to this field for all profiles that will use the Mailchimp integration.

In “Setup” navigate to “Object Manager”

Then select “MC Audience”

And in Fields & Relationships select “Prompt For Full Sync”

Then go to the field level security and grant at least read permissions to all profiles that use the Mailchimp integration

It seems that “read” is all that is required, but I don’t know that for a fact.

I’m just astonished that this newly introduced field seems to be a necessity, yet the managed package does not provide any permissions for this field. Additionally I would have expected Mailchimp to quickly remedy this issue. Luckily for ConcisCons customers, I had seen that problem before they experienced problems with that.

Additionally, it seems that these missing permissions in a managed package is not a first for Mailchimp: the MC Setup page is returning an error

Roland XP-80 – dead with lights on

Views: 1455

So I have a Roland XP-80 with a similar issue as this guy who shows us on Youtube how to replace the battery in the thing. The LCD backlight is on as is the LED on the disk drive. Apart from that – no sign of life.

For me it was an easy fix, as it was quite obvious what had happened:

Capacitor 201 (47µF, 6V) had blown up quite spectacularly. To remedy that, I soldered in one that I had lying around (not exactly the same type, mine has a rating of 25 Volt), and after that all was well.

The stain on the board was quite substantial, so I had to carefully scratch the sod away to try and see if the traces were still in good condition (it always helps if you have access to a service manual / service notes, you can find some at synfo.nl). And yes, all was still connected according to my multi meter.

This would have been a job for a few minutes if I had done that in a sensible manner – but no, I had to start too far down the path.

Remember the simplest steps to find an issue with electronics (I think Louis Rossmann and Dave Jones deserve a bit of credit here):

  • Sniff the board – really, that was a dead give-away here. It stank of exploded component. I just ignored it.
  • Visual inspection – if it doesn’t smell funny, you should still look for the sh*t stain on the board. As you can see above, another dead give-away. I just ignored it.
  • Thou shall check voltages – And this is where I started at. At the power supply. Which was basically fine.

Now, after these steps you can start using your brain, try to check clocks – which was what I was going to do next, but luckily the chip I wanted to probe was close to the exploded capacitor. You can follow signals, measure in circuit, desolder components to measure outside of the circuit and what not.

But only after the sniff test and the visual inspection! Took me the better part of three hours because I didn’t.

Salesforce: rendering of Visual Force pages and context

Views: 491

In a simple, straightforward programming situation, you would assume that functions are executed when they are seemingly called. When you are rendering a Visual Force page in Salesforce, things seem to be a bit mixed up. Though it seems counter-intuitive, it is understandable what happens.

Let’s take a simple example of a Visual Force page with a controller.

Page (atest):

<apex:page controller="Atest">
    Parameter: {!parameter} <br />
    Parameter2: {!parameter2} <br />
    Parameter: {!parameter} <br />
    Parameter2: {!parameter2} <br />
    Parameter: {!parameter} <br />
    Parameter2: {!parameter2} <br>
</apex:page>

Controller:

public class Atest {
    private Integer parameter;

    public Atest () {
        System.debug ('Constructor called.');
        parameter = 1;
    }

    public Integer getParameter () {
        System.debug ('getParameter');
        parameter = parameter + 1;
        return parameter;
    }

    public Integer getParameter2 () {
        System.debug ('getParameter2');
        parameter = parameter + 1;
        return parameter;
    }

    public PageReference nextPage () {
        PageReference page1 = new PageReference('/apex/atest2');
        Blob b = page1.getContentAsPDF();
        PageReference page2 = new PageReference('/apex/atest2');  
        b = page2.getContentAsPDF();
        PageReference page3 = new PageReference('/apex/atest2');  
        return page3;
    }
}

{!parameter} is a reference to the method getParameter () and {!parameter2} a reference to getParameter2 (). Ignore the method nextPage () for now…

So what you might expect is that the Visual Force renderer calls getParameter (). This increases the variable parameter by 1 and returns its new value. We do see the output “Parameter: 2” – as expected. Then the renderer calls getParameter2 (). This again increases the variable parameter by 1 and returns its new value. We do see the output “Parameter2: 3” – as expected.

Next, we want “parameter” again – seemingly a call to the method getParameter (). But now the method is not actually executed; parameter is not increased anymore. We get the outputs “Parameter: 2” and “Parameter2: 3” again and again, no matter how many times we think the method is called.

Now for the second part in the controller above, we need the VF page atest2, which is virtually the same, except that it is a different file. Also, for convenience to call the method in our class, add

<apex:form>
    <apex:commandButton action="{!nextPage}" value="next page" />
</apex:form>

to the page atest. When you now click on “next page”, the page atest2 is created 3 times. To make sure that it is actually rendered, we get the content of the page as a pdf, and the third time, the page is returned as a PageReference. Therefore you are transported to the page atest2.

What you now see is “Parameter: 4” and “Parameter2: 5”. Even though we have rendered the page atest2 3 times, the variable parameter has only been increased by 1 two times.

This is because the renderer works in the same context for all 3 times it renders atest2. getParameter () and getParameter2 () are both called exactly once, and that only, because we are rendering a page in a new context – the call to the method nextPage (). You could even create the pages atest3 and atest4, have them rendered after each other (in one method), and “Parameter” and “Parameter2” will be the same value for each rendered page.

Any output of a method is directly cached, and the method is not called again, except for if you force a rerender – because that is what re-rendering is for. If you know that values will change, you have to instruct Visual Force to do a new rendering.

To get around this, make sure that you create a new context for a new page with changing information. The easiest way to do so is IMHO to create a controller instance for every page that needs to be rendered, and this in turn can be done by having a different controller for the subsequent pages.

tl;dr:

Do not change the information of a variable or method during one rendering context. Have all information be calculated before anything is actually rendered, and do not change information when using a get-method. If information changes due to user input, have the sections that show information based on the input rerendered when necessary.

Overall: within one context the VF renderer will always call any getter only once.

Was soll das Gerede in Warteschleifen?

Views: 331

Jetzt bin ich wieder in so einer Hotline-Warteschleife (ich möchte doch mal wissen, was Medion zu den widersprüchlichen Angaben zu dem Rechner zu sagen hat), und werde mit Musik genervt.

Aber die Unsitte, dass alle paar Sekunden eine Bandansage mich darüber informiert, dass immer noch niemand zur Verfügung steht, ist extrem störend. Wenn einfach nur Musik läuft, dann schalte ich halt den Lautsprecher an, lasse das Telefon neben mir liegen, und kann weiter arbeiten. Eine menschliche Stimme allerdings reisst mich dann raus – es gibt mir Hoffnung, dass jemand mit mir reden, ja vielleicht sogar mein Problem lösen könnte.

Wirklich: lasst diese sinnlosen Ansagen. Es glaubt doch sowieso niemand, was da geredet wird. “Nur noch ein paar Augenblicke..”, “Der nächste freie Mitarbeiter…” Blah blah blah. Es nervt!

Aldi liefert nicht übermäßig schnell – Medion Erazer X67015

Views: 1105

Am Montag, 4.12. direkt morgens im Aldi bezahlt, aber bis heute noch nicht einmal verschickt.

Den Medion Erazer X67015 hätte ich gerne. Ich hatte schon angefangen, mir Preise für die Komponenten raus zu suchen, und bin dann zu einem ähnlichen Gesamtpreis gelangt. Allerdings hätte ich dann in 3 Shops bestellen müssen, und das Ding noch zusammen bauen. So entspricht der Rechner in etwa der Leistungskategorie, die ich wollte, aber vor allem ist alles zusammen. Mit Garantie.

Allerdings fand ich das Konzept schon etwas gewöhnungsbedürftig. Ich muss also vor Ort im Laden mein Geld abgeben, erhalte dafür einen flimsigen Zettel mit einem Code, und muss dann das ganze Prozedere einer Online-Bestellung noch zusätzlich machen. Und dann halt darauf warten, dass das Gerät auch zu mir kommt.

Dienstag morgen, 0:30h erhalte ich eine Email von Aldi:
“die folgende Bestellung werden wir heute versenden:
[…]
Versanddatum: 04.12.2017”

Also erstmal: die Email ging am 5.12. raus. Sie war im Futur geschrieben und behauptet, am 4.12. würde die Bestellung versendet werden.

Aber ich freute mich dennoch, das schien ja schnell zu funktionieren. Schwierigkeiten mit den Zeitformen der deutschen Sprache kann ich gut ignorieren. Aber jetzt haben wir den 6.12., 2 Tage nachdem die Bestellung versandt werden sollte. Der Status bei DHL ist immer noch, dass die Daten elektronisch übermittelt wurden, aber auch DHL noch auf das Paket wartet:

Sendungsverfolgung DHL: am 5.12. wurden die Daten elektronisch übermitteltIch bin gespannt, ob und wann da noch etwas passiert. Ich bin vor allem sauer, weil die Email eine ganz andere Zusage enthielt. Und das ist einfach nicht in Ordnung!

Telefonisch war auch keine andere Information zu erhalten. Die Person im Callcenter sagte nur, er wisse nicht, was die verschiedenen Firmen untereinander ausgemacht haben. Wie üblich bin ich als Kunde wieder Wurst.